<?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年,可以根据需要调整
