Laravel雪花算法怎么实现

wen PHP项目 14

本文目录导读:

Laravel雪花算法怎么实现

  1. 安装依赖包
  2. 创建雪花ID服务提供者
  3. Redis序列号解析器实现
  4. Laravel缓存序列号解析器(可选)
  5. 创建配置文件和辅助函数
  6. 创建Trait(可选)
  7. 在模型中使用
  8. 简单实现示例
  9. 使用示例
  10. 注意事项

我来详细介绍Laravel中雪花算法的实现方式。

安装依赖包

推荐使用 godruoyi/php-snowflake 包:

composer require godruoyi/php-snowflake

创建雪花ID服务提供者

<?php
namespace App\Providers;
use Godruoyi\Snowflake\Snowflake;
use Godruoyi\Snowflake\SequenceResolver;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Redis;
class SnowflakeServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('snowflake', function ($app) {
            $snowflake = new Snowflake();
            // 设置数据中心ID和工作节点ID
            $datacenterId = config('snowflake.datacenter_id', 1);
            $workerId = config('snowflake.worker_id', 1);
            $snowflake->setDatacenterId($datacenterId)
                     ->setWorkerId($workerId);
            // 可选:设置序列号解析器(支持Redis、Laravel缓存等)
            $snowflake->setSequenceResolver($this->getSequenceResolver());
            return $snowflake;
        });
    }
    /**
     * 获取序列号解析器
     */
    protected function getSequenceResolver()
    {
        // 使用Redis作为序列号解析器(推荐)
        return app(RedisSequenceResolver::class);
    }
}

Redis序列号解析器实现

<?php
namespace App\Providers;
use Godruoyi\Snowflake\SequenceResolver;
use Illuminate\Support\Facades\Redis;
class RedisSequenceResolver implements SequenceResolver
{
    /**
     * 获取序列号
     */
    public function sequence(int $currentTime)
    {
        $key = sprintf('snowflake:%s', $currentTime);
        // 使用Redis INCR命令获取自增序列
        $sequence = Redis::incr($key);
        // 设置过期时间,防止key无限增长
        Redis::expire($key, 10);
        // 最大序列号限制(通常为4095)
        if ($sequence > 4095) {
            // 如果达到上限,等待下一毫秒
            usleep(1000);
            return $this->sequence(time() * 1000);
        }
        return $sequence;
    }
}

Laravel缓存序列号解析器(可选)

<?php
namespace App\Providers;
use Godruoyi\Snowflake\SequenceResolver;
use Illuminate\Support\Facades\Cache;
class CacheSequenceResolver implements SequenceResolver
{
    /**
     * 使用Laravel缓存作为序列号解析器
     */
    public function sequence(int $currentTime)
    {
        $key = sprintf('snowflake:%s', $currentTime);
        // 使用Cache的increment方法
        if (!Cache::has($key)) {
            Cache::put($key, 0, 10);
        }
        $sequence = Cache::increment($key);
        if ($sequence > 4095) {
            usleep(1000);
            return $this->sequence(time() * 1000);
        }
        return $sequence;
    }
}

创建配置文件和辅助函数

配置文件 config/snowflake.php

<?php
return [
    // 数据中心ID(0-31)
    'datacenter_id' => env('SNOWFLAKE_DATACENTER_ID', 1),
    // 工作节点ID(0-31)
    'worker_id' => env('SNOWFLAKE_WORKER_ID', 1),
    // 开始时间戳(毫秒)
    'start_time' => strtotime('2020-01-01') * 1000,
    // 是否启用缓存序列号解析器
    'use_cache_resolver' => env('SNOWFLAKE_USE_CACHE', false),
];

辅助函数

// 在 app/helpers.php 中
if (!function_exists('snowflake_id')) {
    function snowflake_id()
    {
        return app('snowflake')->id();
    }
}

创建Trait(可选)

<?php
namespace App\Models\Traits;
use Godruoyi\Snowflake\Snowflake;
trait HasSnowflakeId
{
    public static function bootHasSnowflakeId()
    {
        static::creating(function ($model) {
            if (!$model->getKey()) {
                $model->{$model->getKeyName()} = app('snowflake')->id();
            }
        });
    }
}

在模型中使用

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Traits\HasSnowflakeId;
class Order extends Model
{
    use HasSnowflakeId;
    // 关闭自增ID
    public $incrementing = false;
    // 设置ID类型为string(如果是数字ID可以去掉)
    protected $keyType = 'string';
    // 其他模型代码...
}

简单实现示例

如果不想使用第三方包,也可以自己实现简单的雪花算法:

<?php
namespace App\Services;
class SnowflakeService
{
    // 时间戳起始值(毫秒)
    const EPOCH = 1577808000000; // 2020-01-01
    // 各部分位数
    const TIME_BITS = 41;
    const DATACENTER_BITS = 5;
    const WORKER_BITS = 5;
    const SEQUENCE_BITS = 12;
    // 最大值
    const MAX_DATACENTER_ID = 31;
    const MAX_WORKER_ID = 31;
    const MAX_SEQUENCE = 4095;
    // 位移量
    const TIME_SHIFT = 22;
    const DATACENTER_SHIFT = 17;
    const WORKER_SHIFT = 12;
    protected $datacenterId;
    protected $workerId;
    protected $lastTimestamp = -1;
    protected $sequence = 0;
    public function __construct($datacenterId = 1, $workerId = 1)
    {
        if ($datacenterId > self::MAX_DATACENTER_ID || $datacenterId < 0) {
            throw new \InvalidArgumentException('数据中心ID范围错误');
        }
        if ($workerId > self::MAX_WORKER_ID || $workerId < 0) {
            throw new \InvalidArgumentException('工作节点ID范围错误');
        }
        $this->datacenterId = $datacenterId;
        $this->workerId = $workerId;
    }
    public function generateId()
    {
        $timestamp = $this->getTimestamp();
        if ($timestamp < $this->lastTimestamp) {
            throw new \RuntimeException('时钟回拨异常');
        }
        if ($timestamp === $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & self::MAX_SEQUENCE;
            if ($this->sequence === 0) {
                $timestamp = $this->waitNextMillis($timestamp);
            }
        } else {
            $this->sequence = 0;
        }
        $this->lastTimestamp = $timestamp;
        return (($timestamp - self::EPOCH) << self::TIME_SHIFT)
            | ($this->datacenterId << self::DATACENTER_SHIFT)
            | ($this->workerId << self::WORKER_SHIFT)
            | $this->sequence;
    }
    protected function getTimestamp()
    {
        return (int) (microtime(true) * 1000);
    }
    protected function waitNextMillis($lastTimestamp)
    {
        $timestamp = $this->getTimestamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getTimestamp();
        }
        return $timestamp;
    }
}

使用示例

// 生成ID
$id = app('snowflake')->id();
// 或
$id = snowflake_id();
// 解析ID
$snowflake = app('snowflake');
$parsed = $snowflake->parseId($id);
// 返回数组包含:timestamp, sequence, workerid, datacenterid

注意事项

  1. 时钟回拨问题:生产环境建议使用NTP时间同步
  2. 序列号溢出:当同一毫秒生成超过4095个ID时,需要等待下一毫秒
  3. 数据中心ID:如果是单机部署,设为1即可;分布式系统需要确保唯一性
  4. 性能考虑:使用Redis作为序列号解析器性能更好

这样你就得到了一个完整的Laravel雪花算法实现方案。

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