优化队列
This commit is contained in:
@@ -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']
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,27 @@
|
||||
<?php
|
||||
// application/extra/snowflake.php
|
||||
|
||||
return [
|
||||
'worker_id' => 1, // 工作ID(1-31)
|
||||
'datacenter_id' => 1, // 数据中心ID(1-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' => '',
|
||||
];
|
||||
Reference in New Issue
Block a user