本文目录导读:

我来详细介绍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
注意事项
- 时钟回拨问题:生产环境建议使用NTP时间同步
- 序列号溢出:当同一毫秒生成超过4095个ID时,需要等待下一毫秒
- 数据中心ID:如果是单机部署,设为1即可;分布式系统需要确保唯一性
- 性能考虑:使用Redis作为序列号解析器性能更好
这样你就得到了一个完整的Laravel雪花算法实现方案。