<?php
/**
* 雪花算法PHP实现
* 64位ID结构:1位符号位 + 41位时间戳 + 10位机器ID + 12位序列号
*/
class Snowflake
{
// 基础常量
const EPOCH = 1288834974657; // 起始时间戳 (2010-11-04 01:42:54 UTC)
// 位数分配
const TIMESTAMP_BITS = 41; // 时间戳位数
const MACHINE_BITS = 10; // 机器ID位数
const SEQUENCE_BITS = 12; // 序列号位数
// 最大值计算
const MAX_MACHINE_ID = -1 ^ (-1 << self::MACHINE_BITS); // 1023
const MAX_SEQUENCE = -1 ^ (-1 << self::SEQUENCE_BITS); // 4095
// 位移量
const MACHINE_SHIFT = self::SEQUENCE_BITS; // 12
const TIMESTAMP_SHIFT = self::SEQUENCE_BITS + self::MACHINE_BITS; // 22
// 掩码
const SEQUENCE_MASK = self::MAX_SEQUENCE; // 4095
/** @var int 机器ID */
private $machineId;
/** @var int 数据中心ID */
private $datacenterId;
/** @var int 上次生成ID的时间戳 */
private $lastTimestamp = -1;
/** @var int 当前序列号 */
private $sequence = 0;
/** @var int 进程唯一标识(可选) */
private $processId = 0;
/** @var array 用于存储最近生成的ID(防止时钟回拨) */
private $lastIds = [];
/**
* 构造函数
* @param int $machineId 机器ID (0-31)
* @param int $datacenterId 数据中心ID (0-31)
* @param int $processId 可选进程ID(用于增加唯一性)
*/
public function __construct($machineId = 1, $datacenterId = 1, $processId = 0)
{
// 验证数据中心ID
if ($datacenterId > self::MAX_MACHINE_ID >> (self::MACHINE_BITS / 2) || $datacenterId < 0) {
throw new InvalidArgumentException(sprintf('数据中心ID必须在0到%d之间', self::MAX_MACHINE_ID >> (self::MACHINE_BITS / 2)));
}
// 验证机器ID
if ($machineId > self::MAX_MACHINE_ID >> (self::MACHINE_BITS / 2) || $machineId < 0) {
throw new InvalidArgumentException(sprintf('机器ID必须在0到%d之间', self::MAX_MACHINE_ID >> (self::MACHINE_BITS / 2)));
}
$this->machineId = $machineId;
$this->datacenterId = $datacenterId;
$this->processId = $processId;
// 合并机器ID和数据中心ID
$this->machineId = ($datacenterId << (self::MACHINE_BITS / 2)) | $machineId;
}
/**
* 生成下一个唯一ID
* @return int 64位整数ID
*/
public function nextId()
{
$timestamp = $this->currentTimeMillis();
// 处理时钟回拨
if ($timestamp < $this->lastTimestamp) {
$offset = $this->lastTimestamp - $timestamp;
// 如果差异小于100ms,等待时间追上,否则抛出异常
if ($offset < 100) {
usleep($offset * 1000);
$timestamp = $this->currentTimeMillis();
// 如果仍然小于上次时间戳,说明时钟回拨严重
if ($timestamp < $this->lastTimestamp) {
throw new RuntimeException('时钟回拨错误,拒绝生成ID');
}
} else {
// 检查是否可以使用上次生成时间戳的补位ID
if (isset($this->lastIds) && count($this->lastIds) > 0) {
// 尝试从缓存中获取一个未使用的ID
$cachedId = $this->getCachedId();
if ($cachedId !== null) {
return $cachedId;
}
}
throw new RuntimeException('时钟回拨错误,拒绝生成ID');
}
}
// 同一毫秒内
if ($this->lastTimestamp === $timestamp) {
// 序列号递增
$this->sequence = ($this->sequence + 1) & self::SEQUENCE_MASK;
// 如果序列号溢出,等待下一毫秒
if ($this->sequence === 0) {
$timestamp = $this->waitNextMillis($this->lastTimestamp);
}
} else {
// 重新计时
$this->sequence = 0;
}
$this->lastTimestamp = $timestamp;
// 组合ID
$id = (($timestamp - self::EPOCH) << self::TIMESTAMP_SHIFT)
| ($this->machineId << self::MACHINE_SHIFT)
| $this->sequence;
// 缓存最近生成的ID(用于时钟回拨处理)
$this->lastIds[] = $id;
if (count($this->lastIds) > 100) {
array_shift($this->lastIds);
}
return $id;
}
/**
* 批量生成ID
* @param int $count 生成数量
* @return array
*/
public function nextIds($count = 10)
{
if ($count <= 0 || $count > 1000) {
throw new InvalidArgumentException('批量生成数量必须在1到1000之间');
}
$ids = [];
for ($i = 0; $i < $count; $i++) {
$ids[] = $this->nextId();
}
return $ids;
}
/**
* 解析ID的组成部分
* @param int $id 雪花ID
* @return array
*/
public function parseId($id)
{
$binary = str_pad(decbin($id), 64, '0', STR_PAD_LEFT);
return [
'binary' => $binary,
'timestamp' => (($id >> self::TIMESTAMP_SHIFT) + self::EPOCH),
'machineId' => ($id >> self::MACHINE_SHIFT) & self::MAX_MACHINE_ID,
'sequence' => $id & self::SEQUENCE_MASK,
'decimal' => $id
];
}
/**
* 获取当前时间戳(毫秒)
* @return int
*/
private function currentTimeMillis()
{
return (int) (microtime(true) * 1000);
}
/**
* 等待下一毫秒
* @param int $lastTimestamp
* @return int
*/
private function waitNextMillis($lastTimestamp)
{
$timestamp = $this->currentTimeMillis();
while ($timestamp <= $lastTimestamp) {
$timestamp = $this->currentTimeMillis();
}
return $timestamp;
}
/**
* 幂等性检查(可选)
* 用于处理时钟回拨时的缓存ID
* @return int|null
*/
private function getCachedId()
{
// 这里可以实现更复杂的缓存逻辑
// 简单实现:返回最后一个可用的缓存ID
if (count($this->lastIds) > 0) {
return end($this->lastIds);
}
return null;
}
/**
* 设置起始时间戳(可选)
* @param int $epoch
*/
public function setEpoch($epoch)
{
if (!is_int($epoch) || $epoch < 0) {
throw new InvalidArgumentException('无效的时间戳');
}
self::$epoch = $epoch;
}
/**
* 静态变量用于测试
* @var int
*/
private static $epoch = self::EPOCH;
/**
* 性能测试方法(可选)
* @param int $times 测试次数
*/
public function benchmark($times = 1000)
{
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
$this->nextId();
}
$end = microtime(true);
$elapsed = ($end - $start) * 1000; // 毫秒
printf("生成 %d 个ID耗时: %.2f ms,每秒可生成: %.0f 个\n",
$times,
$elapsed,
$times / ($elapsed / 1000)
);
}
}
// 使用示例
try {
// 创建雪花ID生成器
$snowflake = new Snowflake(1, 1); // 机器ID=1, 数据中心ID=1
// 生成单个ID
$id = $snowflake->nextId();
echo "单ID: " . $id . "\n";
// 批量生成
$ids = $snowflake->nextIds(5);
echo "批量ID: " . implode(', ', $ids) . "\n";
// 解析ID
$parsed = $snowflake->parseId($id);
echo "ID解析:\n";
print_r($parsed);
// 性能测试
$snowflake->benchmark(10000);
} catch (Exception $e) {
echo "错误: " . $e->getMessage() . "\n";
}]
使用说明
基本用法
// 初始化 $snowflake = new Snowflake(1, 1); // 机器ID=1, 数据中心ID=1 // 生成ID $id = $snowflake->nextId(); // 批量生成 $ids = $snowflake->nextIds(100);
ID结构说明
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| unused | timestamp (41 bits) | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| machine id (10 bits) | sequence (12 bits)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
特性
- 唯一性: 支持分布式环境下生成唯一ID
- 高性能: 每秒可生成数百万个ID
- 趋势递增: ID大致按照时间递增
- 可解析: 可以从ID中提取时间戳等信息
- 时钟回拨处理: 提供了一定的容错机制
注意事项
- 需要确保系统时钟同步(推荐使用NTP)
- 集群环境需要为每个节点配置不同的机器ID
- 默认起始时间设置为2010年,可以根据需要调整

版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播