PHP雪花算法实现

wen PHP项目 1
<?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年,可以根据需要调整

PHP雪花算法实现

抱歉,评论功能暂时关闭!