优化队列

This commit is contained in:
2026-01-13 23:41:55 +08:00
parent 4765a738c0
commit aa0615974b
2 changed files with 420 additions and 48 deletions

View File

@@ -5,121 +5,471 @@ namespace app\common\library;
class Snowflake
{
// 起始时间戳2023-01-01
// 起始时间戳2023-01-01 00:00:00
const EPOCH = 1672502400000;
// 机器ID位数
const WORKER_ID_BITS = 5;
const DATACENTER_ID_BITS = 5;
const SEQUENCE_BITS = 12;
// 位数分配
const WORKER_ID_BITS = 5; // 5位工作机器ID
const DATACENTER_ID_BITS = 5; // 5位数据中心ID
const SEQUENCE_BITS = 12; // 12位序列号
// 最大值
// 最大值计算
const MAX_WORKER_ID = -1 ^ (-1 << self::WORKER_ID_BITS);
const MAX_DATACENTER_ID = -1 ^ (-1 << self::DATACENTER_ID_BITS);
const MAX_SEQUENCE = -1 ^ (-1 << self::SEQUENCE_BITS);
// 移位
// 移位偏移量
const WORKER_ID_SHIFT = self::SEQUENCE_BITS;
const DATACENTER_ID_SHIFT = self::SEQUENCE_BITS + self::WORKER_ID_BITS;
const TIMESTAMP_LEFT_SHIFT = self::SEQUENCE_BITS + self::WORKER_ID_BITS + self::DATACENTER_ID_BITS;
// 序列号掩码
const SEQUENCE_MASK = -1 ^ (-1 << self::SEQUENCE_BITS);
// 实例存储
protected static $instances = [];
protected $workerId;
protected $datacenterId;
protected $sequence = 0;
protected $lastTimestamp = -1;
protected $lastGenerateTime = 0;
/**
* 构造函数
* @param int $workerId 工作机器ID (0-31)
* @param int $datacenterId 数据中心ID (0-31)
* @throws \Exception
*/
public function __construct($workerId = 1, $datacenterId = 1)
{
// 验证 workerId
if ($workerId > self::MAX_WORKER_ID || $workerId < 0) {
throw new \Exception("worker Id can't be greater than " . self::MAX_WORKER_ID . " or less than 0");
throw new \Exception(sprintf(
"Worker ID 必须在 0 到 %d 之间,当前值:%d",
self::MAX_WORKER_ID,
$workerId
));
}
// 验证 datacenterId
if ($datacenterId > self::MAX_DATACENTER_ID || $datacenterId < 0) {
throw new \Exception("datacenter Id can't be greater than " . self::MAX_DATACENTER_ID . " or less than 0");
throw new \Exception(sprintf(
"Datacenter ID 必须在 0 到 %d 之间,当前值:%d",
self::MAX_DATACENTER_ID,
$datacenterId
));
}
$this->workerId = $workerId;
$this->datacenterId = $datacenterId;
// 记录日志
\think\Log::info(sprintf(
"Snowflake 实例初始化完成WorkerId: %d, DatacenterId: %d",
$workerId,
$datacenterId
));
}
/**
* 生成下一个ID
* @return int
* @throws \Exception
*/
public function nextId()
{
$timestamp = $this->timeGen();
// 检查时钟回拨
if ($timestamp < $this->lastTimestamp) {
$diff = $this->lastTimestamp - $timestamp;
throw new \Exception("Clock moved backwards. Refusing to generate id for {$diff} milliseconds");
// 轻微时钟回拨,等待时钟追上
if ($diff < 100) { // 100毫秒内的回拨
usleep($diff * 1000); // 微秒休眠
$timestamp = $this->timeGen();
} else {
throw new \Exception(sprintf(
"时钟回拨过大拒绝生成ID。回拨时间%d 毫秒",
$diff
));
}
}
// 同一毫秒内生成多个ID
if ($this->lastTimestamp == $timestamp) {
$this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE;
// 序列号溢出,等待下一毫秒
if ($this->sequence == 0) {
$timestamp = $this->tilNextMillis($this->lastTimestamp);
}
} else {
$this->sequence = 0;
// 新的毫秒,重置序列号,使用随机初始值避免重复
$this->sequence = mt_rand(0, 10) & self::MAX_SEQUENCE;
}
$this->lastTimestamp = $timestamp;
return (($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT) |
($this->datacenterId << self::DATACENTER_ID_SHIFT) |
($this->workerId << self::WORKER_ID_SHIFT) |
$this->sequence;
}
// 计算并返回ID
$id = ((($timestamp - self::EPOCH) << self::TIMESTAMP_LEFT_SHIFT)
| ($this->datacenterId << self::DATACENTER_ID_SHIFT)
| ($this->workerId << self::WORKER_ID_SHIFT)
| $this->sequence);
protected function tilNextMillis($lastTimestamp)
{
$timestamp = $this->timeGen();
while ($timestamp <= $lastTimestamp) {
$timestamp = $this->timeGen();
}
return $timestamp;
}
// 记录生成时间(用于调试)
$this->lastGenerateTime = microtime(true);
protected function timeGen()
{
return floor(microtime(true) * 1000);
return $id;
}
/**
* 解析雪花ID
* @param int $id
* 等待到下一毫秒
* @param int $lastTimestamp 最后时间戳
* @return int
*/
protected function tilNextMillis($lastTimestamp)
{
$timestamp = $this->timeGen();
$waitCount = 0;
while ($timestamp <= $lastTimestamp) {
// 等待时间过长记录警告
if ($waitCount++ > 100) {
\think\Log::warning("Snowflake 等待下一毫秒时间过长,已等待 {$waitCount} 次循环");
}
// 微秒级休眠避免CPU占用过高
usleep(100); // 休眠100微秒
$timestamp = $this->timeGen();
}
return $timestamp;
}
/**
* 获取当前时间戳(毫秒)
* @return int
*/
protected function timeGen()
{
// 使用微秒时间,提高精度
return (int)(microtime(true) * 1000);
}
/**
* 解析雪花ID为各个组成部分
* @param int $id 雪花ID
* @return array
*/
public static function parseId($id)
{
// 转换为64位二进制字符串
$binary = decbin($id);
$binary = str_pad($binary, 64, '0', STR_PAD_LEFT);
$timestamp = bindec(substr($binary, 0, 42));
$timestamp = $timestamp + self::EPOCH;
// 提取各组成部分
$timestampBin = substr($binary, 0, 42);
$datacenterIdBin = substr($binary, 42, 5);
$workerIdBin = substr($binary, 47, 5);
$sequenceBin = substr($binary, 52, 12);
$datacenterId = bindec(substr($binary, 42, 5));
$workerId = bindec(substr($binary, 47, 5));
$sequence = bindec(substr($binary, 52, 12));
// 转换为十进制
$timestamp = bindec($timestampBin) + self::EPOCH;
$datacenterId = bindec($datacenterIdBin);
$workerId = bindec($workerIdBin);
$sequence = bindec($sequenceBin);
// 转换为可读时间
$dateTime = date('Y-m-d H:i:s', (int)($timestamp / 1000));
$millisecond = $timestamp % 1000;
return [
'id' => $id,
'timestamp' => $timestamp,
'datacenterId' => $datacenterId,
'workerId' => $workerId,
'sequence' => $sequence
'datetime' => $dateTime,
'millisecond' => $millisecond,
'datacenter_id' => $datacenterId,
'worker_id' => $workerId,
'sequence' => $sequence,
'binary' => $binary,
'parts' => [
'timestamp' => $timestampBin,
'datacenter_id' => $datacenterIdBin,
'worker_id' => $workerIdBin,
'sequence' => $sequenceBin
]
];
}
/**
* 生成ID单例模式
* 生成ID主调用方法
* @param int|null $workerId 工作机器ID
* @param int|null $datacenterId 数据中心ID
* @return int
* @throws \Exception
*/
public static function generate($workerId = null, $datacenterId = null)
{
// 获取配置
if ($workerId === null) {
$workerId = self::getWorkerId();
}
if ($datacenterId === null) {
$datacenterId = self::getDatacenterId();
}
// 创建实例键
$instanceKey = "{$workerId}_{$datacenterId}";
// 检查是否已有实例
if (!isset(self::$instances[$instanceKey])) {
self::$instances[$instanceKey] = new self($workerId, $datacenterId);
}
// 生成ID
return self::$instances[$instanceKey]->nextId();
}
/**
* 获取工作机器ID
* @return int
*/
public static function generate()
protected static function getWorkerId()
{
static $instance = null;
if (!$instance) {
// 从配置获取workerId和datacenterId
$workerId = config('snowflake.worker_id') ?: 1;
$datacenterId = config('snowflake.datacenter_id') ?: 1;
$instance = new self($workerId, $datacenterId);
// 1. 从配置文件获取FastAdmin 使用 application/extra/ 目录)
$config = config('snowflake');
if ($config && isset($config['worker_id'])) {
$workerId = (int)$config['worker_id'];
if ($workerId >= 0 && $workerId <= self::MAX_WORKER_ID) {
return $workerId;
}
}
return $instance->nextId();
// 2. 从环境变量获取FastAdmin 使用 .env 文件)
if (function_exists('env')) {
$envWorkerId = env('SNOWFLAKE_WORKER_ID');
if ($envWorkerId !== null && $envWorkerId !== false) {
$workerId = (int)$envWorkerId;
if ($workerId >= 0 && $workerId <= self::MAX_WORKER_ID) {
return $workerId;
}
}
}
// 3. 自动生成基于服务器信息和进程ID
// 使用服务器IP的最后一段 + 进程ID
$serverIp = self::getServerIp();
$ipParts = explode('.', $serverIp);
$ipLastPart = end($ipParts);
$processId = getmypid() ?: 1;
// 计算唯一的工作ID
$workerId = (($ipLastPart * 1000) + $processId) % (self::MAX_WORKER_ID + 1);
// 确保在有效范围内
$workerId = max(0, min($workerId, self::MAX_WORKER_ID));
return (int)$workerId;
}
/**
* 获取数据中心ID
* @return int
*/
protected static function getDatacenterId()
{
// 1. 从配置文件获取
$config = config('snowflake');
if ($config && isset($config['datacenter_id'])) {
$datacenterId = (int)$config['datacenter_id'];
if ($datacenterId >= 0 && $datacenterId <= self::MAX_DATACENTER_ID) {
return $datacenterId;
}
}
// 2. 从环境变量获取
if (function_exists('env')) {
$envDatacenterId = env('SNOWFLAKE_DATACENTER_ID');
if ($envDatacenterId !== null && $envDatacenterId !== false) {
$datacenterId = (int)$envDatacenterId;
if ($datacenterId >= 0 && $datacenterId <= self::MAX_DATACENTER_ID) {
return $datacenterId;
}
}
}
// 3. 自动生成基于服务器IP
$serverIp = self::getServerIp();
$ipParts = explode('.', $serverIp);
// 使用IP前两段计算数据中心ID
$part1 = isset($ipParts[0]) ? (int)$ipParts[0] : 0;
$part2 = isset($ipParts[1]) ? (int)$ipParts[1] : 0;
$datacenterId = (($part1 * 10) + $part2) % (self::MAX_DATACENTER_ID + 1);
// 确保在有效范围内
$datacenterId = max(0, min($datacenterId, self::MAX_DATACENTER_ID));
return (int)$datacenterId;
}
/**
* 获取服务器IP
* @return string
*/
protected static function getServerIp()
{
// 多种方式获取服务器IP
if (isset($_SERVER['SERVER_ADDR'])) {
return $_SERVER['SERVER_ADDR'];
}
if (function_exists('gethostname')) {
$hostname = gethostname();
if ($hostname) {
$ip = gethostbyname($hostname);
if ($ip !== $hostname) {
return $ip;
}
}
}
// 尝试获取本机IP
if (function_exists('shell_exec') && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
$ip = shell_exec("hostname -I | awk '{print $1}'");
if ($ip) {
return trim($ip);
}
}
// 默认返回127.0.0.1
return '127.0.0.1';
}
/**
* 批量生成ID
* @param int $count 生成数量
* @param int|null $workerId 工作机器ID
* @param int|null $datacenterId 数据中心ID
* @return array
* @throws \Exception
*/
public static function batchGenerate($count = 10, $workerId = null, $datacenterId = null)
{
if ($count <= 0 || $count > 10000) {
throw new \Exception("生成数量必须在 1-10000 之间");
}
$ids = [];
for ($i = 0; $i < $count; $i++) {
$ids[] = self::generate($workerId, $datacenterId);
}
return $ids;
}
/**
* 验证ID是否有效
* @param int $id 雪花ID
* @return bool
*/
public static function validate($id)
{
if (!is_numeric($id) || $id <= 0) {
return false;
}
try {
$parsed = self::parseId($id);
// 检查时间戳是否合理(不能超过当前时间+10年
$currentTime = (int)(microtime(true) * 1000);
$maxTime = $currentTime + (10 * 365 * 24 * 60 * 60 * 1000); // 10年后
if ($parsed['timestamp'] > $maxTime || $parsed['timestamp'] < self::EPOCH) {
return false;
}
// 检查ID组成部分是否在有效范围内
if ($parsed['datacenter_id'] > self::MAX_DATACENTER_ID ||
$parsed['worker_id'] > self::MAX_WORKER_ID ||
$parsed['sequence'] > self::MAX_SEQUENCE) {
return false;
}
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 获取ID生成统计信息
* @return array
*/
public function getStats()
{
return [
'worker_id' => $this->workerId,
'datacenter_id' => $this->datacenterId,
'last_timestamp' => $this->lastTimestamp,
'last_generate_time' => $this->lastGenerateTime,
'max_worker_id' => self::MAX_WORKER_ID,
'max_datacenter_id' => self::MAX_DATACENTER_ID,
'max_sequence' => self::MAX_SEQUENCE,
'epoch' => self::EPOCH,
'epoch_datetime' => date('Y-m-d H:i:s', self::EPOCH / 1000)
];
}
/**
* 获取ID的详细信息调试用
* @param int $id
* @return array
*/
public static function getDetail($id)
{
if (!self::validate($id)) {
return ['error' => '无效的雪花ID'];
}
$parsed = self::parseId($id);
// 添加生成时间估算
$generateTime = date('Y-m-d H:i:s', $parsed['timestamp'] / 1000);
$now = time();
$generateTimestamp = $parsed['timestamp'] / 1000;
$timeDiff = $now - $generateTimestamp;
if ($timeDiff < 60) {
$timeAgo = "刚刚";
} elseif ($timeDiff < 3600) {
$timeAgo = floor($timeDiff / 60) . "分钟前";
} elseif ($timeDiff < 86400) {
$timeAgo = floor($timeDiff / 3600) . "小时前";
} else {
$timeAgo = floor($timeDiff / 86400) . "天前";
}
return [
'id' => $parsed['id'],
'timestamp' => $parsed['timestamp'],
'datetime' => $parsed['datetime'],
'datetime_full' => $generateTime . '.' . str_pad($parsed['millisecond'], 3, '0', STR_PAD_LEFT),
'time_ago' => $timeAgo,
'datacenter_id' => $parsed['datacenter_id'],
'worker_id' => $parsed['worker_id'],
'sequence' => $parsed['sequence'],
'binary' => chunk_split($parsed['binary'], 8, ' '),
'binary_parts' => [
'timestamp' => $parsed['parts']['timestamp'],
'datacenter' => $parsed['parts']['datacenter_id'],
'worker' => $parsed['parts']['worker_id'],
'sequence' => $parsed['parts']['sequence']
]
];
}
}

View File

@@ -1,5 +1,27 @@
<?php
// application/extra/snowflake.php
return [
'worker_id' => 1, // 工作ID1-31
'datacenter_id' => 1, // 数据中心ID1-31
// 工作机器ID (0-31)
// -1 表示自动生成
'worker_id' => -1,
// 数据中心ID (0-31)
// -1 表示自动生成
'datacenter_id' => -1,
// 是否启用日志记录
'log_enabled' => false,
// 时钟回拨处理策略
'clock_backwards_strategy' => 'wait',
// 最大等待时间(毫秒)
'max_wait_time' => 100,
// ID生成模式
'mode' => 'auto',
// 服务器标识
'server_identifier' => '',
];