PHP项目雪花算法生成ID

wen PHP项目 3

本文目录导读:

PHP项目雪花算法生成ID

  1. 基础版雪花算法实现
  2. 改进版:支持更灵活的配置
  3. 带缓存和锁的线程安全版本
  4. 批量生成ID(性能优化)
  5. 使用建议
  6. 测试示例

我来为您提供一个PHP实现的雪花算法(Snowflake ID)生成器,这是一个分布式ID生成方案,能够生成全局唯一的64位整数ID。

基础版雪花算法实现

<?php
class Snowflake
{
    // 各个部分的位数
    const EPOCH_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
    // 起始时间戳(2020-01-01)
    private const EPOCH = 1577808000000;
    private $machineId;      // 机器ID
    private $sequence = 0;   // 序列号
    private $lastTimestamp = -1;  // 上次生成ID的时间戳
    /**
     * 构造函数
     * @param int $machineId 机器ID (0-1023)
     * @throws Exception
     */
    public function __construct($machineId = 0)
    {
        if ($machineId < 0 || $machineId > self::MAX_MACHINE_ID) {
            throw new Exception("Machine ID must be between 0 and " . self::MAX_MACHINE_ID);
        }
        $this->machineId = $machineId;
    }
    /**
     * 生成新的ID
     * @return int 64位雪花算法ID
     * @throws Exception
     */
    public function nextId()
    {
        $timestamp = $this->getCurrentTimestamp();
        // 处理时钟回拨
        if ($timestamp < $this->lastTimestamp) {
            throw new Exception("Clock moved backwards. Refusing to generate id for " . 
                ($this->lastTimestamp - $timestamp) . " milliseconds");
        }
        // 同一毫秒内生成ID
        if ($timestamp === $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE;
            // 序列号用尽,等待下一毫秒
            if ($this->sequence === 0) {
                $timestamp = $this->waitNextMillis($this->lastTimestamp);
            }
        } else {
            // 新的毫秒,序列号从0开始
            $this->sequence = 0;
        }
        $this->lastTimestamp = $timestamp;
        // 组合生成ID
        return (($timestamp - self::EPOCH) << self::TIMESTAMP_SHIFT) |
               ($this->machineId << self::MACHINE_SHIFT) |
               $this->sequence;
    }
    /**
     * 等待到下一毫秒
     */
    private function waitNextMillis($lastTimestamp)
    {
        $timestamp = $this->getCurrentTimestamp();
        while ($timestamp <= $lastTimestamp) {
            usleep(100);  // 等待100微秒
            $timestamp = $this->getCurrentTimestamp();
        }
        return $timestamp;
    }
    /**
     * 获取当前毫秒时间戳
     */
    private function getCurrentTimestamp()
    {
        return (int)(microtime(true) * 1000);
    }
    /**
     * 解析ID,获取其组成部分(用于调试)
     */
    public function parseId($id)
    {
        $timestamp = ($id >> self::TIMESTAMP_SHIFT) + self::EPOCH;
        $machineId = ($id >> self::MACHINE_SHIFT) & self::MAX_MACHINE_ID;
        $sequence = $id & self::MAX_SEQUENCE;
        return [
            'timestamp' => $timestamp,
            'datetime' => date('Y-m-d H:i:s', $timestamp / 1000),
            'machine_id' => $machineId,
            'sequence' => $sequence
        ];
    }
}
// 使用示例
try {
    $snowflake = new Snowflake(1);  // 机器ID为1
    // 生成多个ID
    $ids = [];
    for ($i = 0; $i < 10; $i++) {
        $ids[] = $snowflake->nextId();
    }
    print_r($ids);
    // 解析第一个ID
    print_r($snowflake->parseId($ids[0]));
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

改进版:支持更灵活的配置

<?php
class FlexibleSnowflake
{
    // 默认配置(Twitter Snowflake标准)
    private $config = [
        'epoch' => 1577808000000,        // 起始时间
        'timestamp_bits' => 41,          // 时间戳位数
        'machine_bits' => 10,            // 机器ID位数
        'sequence_bits' => 12,           // 序列号位数
    ];
    private $machineId;
    private $sequence = 0;
    private $lastTimestamp = -1;
    // 计算后的值
    private $maxMachineId;
    private $maxSequence;
    private $machineShift;
    private $timestampShift;
    public function __construct($machineId = 0, $config = [])
    {
        // 合并配置
        $this->config = array_merge($this->config, $config);
        // 计算位偏移
        $this->machineShift = $this->config['sequence_bits'];
        $this->timestampShift = $this->config['sequence_bits'] + $this->config['machine_bits'];
        // 计算最大值
        $this->maxMachineId = -1 ^ (-1 << $this->config['machine_bits']);
        $this->maxSequence = -1 ^ (-1 << $this->config['sequence_bits']);
        // 验证机器ID
        if ($machineId < 0 || $machineId > $this->maxMachineId) {
            throw new Exception("Machine ID must be between 0 and " . $this->maxMachineId);
        }
        $this->machineId = $machineId;
    }
    public function nextId()
    {
        $timestamp = $this->getTimestamp();
        if ($timestamp < $this->lastTimestamp) {
            throw new Exception("Clock moved backwards");
        }
        if ($timestamp === $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & $this->maxSequence;
            if ($this->sequence === 0) {
                $timestamp = $this->waitNextMillis();
            }
        } else {
            $this->sequence = 0;
        }
        $this->lastTimestamp = $timestamp;
        return (($timestamp - $this->config['epoch']) << $this->timestampShift) |
               ($this->machineId << $this->machineShift) |
               $this->sequence;
    }
    private function getTimestamp()
    {
        return (int)(microtime(true) * 1000);
    }
    private function waitNextMillis()
    {
        $timestamp = $this->getTimestamp();
        while ($timestamp <= $this->lastTimestamp) {
            usleep(100);
            $timestamp = $this->getTimestamp();
        }
        return $timestamp;
    }
}

带缓存和锁的线程安全版本

<?php
class ThreadSafeSnowflake extends Snowflake
{
    private $lockFile;
    private $fp;
    public function __construct($machineId = 0, $lockPath = '/tmp/')
    {
        parent::__construct($machineId);
        // 创建锁文件(用于多进程/多线程场景)
        $this->lockFile = $lockPath . 'snowflake_' . $machineId . '.lock';
        $this->fp = fopen($this->lockFile, 'c');
        if (!$this->fp) {
            throw new Exception("Cannot open lock file");
        }
    }
    public function nextId()
    {
        // 文件锁确保并发安全
        if (!flock($this->fp, LOCK_EX)) {
            throw new Exception("Cannot acquire lock");
        }
        try {
            return parent::nextId();
        } finally {
            flock($this->fp, LOCK_UN);
        }
    }
    public function __destruct()
    {
        if ($this->fp) {
            fclose($this->fp);
        }
    }
}

批量生成ID(性能优化)

<?php
class BatchSnowflake extends Snowflake
{
    /**
     * 批量生成ID
     * @param int $batchSize 批量大小
     * @return array
     */
    public function nextBatchId($batchSize = 100)
    {
        $ids = [];
        for ($i = 0; $i < $batchSize; $i++) {
            $ids[] = $this->nextId();
        }
        return $ids;
    }
    /**
     * 预生成一批ID并缓存
     */
    private $idCache = [];
    public function getCachedId()
    {
        if (empty($this->idCache)) {
            $this->idCache = $this->nextBatchId(100);
        }
        return array_shift($this->idCache);
    }
}

使用建议

  1. 机器ID分配:在多服务器环境中,每个节点使用不同的machineId
  2. 时钟同步:确保服务器时钟差异小于10ms
  3. 并发控制:高并发场景使用锁或Redis原子操作
  4. ID格式:返回的ID可以作为字符串处理,避免PHP整数溢出

测试示例

// 测试代码
$snowflake = new Snowflake(1);
$startTime = microtime(true);
$testCount = 10000;
for ($i = 0; $i < $testCount; $i++) {
    $id = $snowflake->nextId();
    if ($i < 5) {
        echo "ID: " . $id . "\n";
    }
}
$endTime = microtime(true);
$timeUsed = $endTime - $startTime;
echo "生成 " . $testCount . " 个ID耗时: " . $timeUsed . " 秒\n";
echo "平均速度: " . ($testCount / $timeUsed) . " IDs/秒\n";
echo "内存占用: " . memory_get_peak_usage(true) / 1024 . " KB\n";

这个实现包含了完整的雪花算法核心功能,你可以根据实际需求选择使用基础版或功能增强版,记得根据你的PHP环境(32位还是64位)进行相应调整。

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