在PHP项目中,当业务从单体架构转向分布式部署,数据库主键不能简单依赖自增字段。Snowflake雪花算法将64位整数划分为符号位、时间戳、机器ID和序列号,能在多节点下快速产出唯一且基本有序的主键。

一、Snowflake算法结构
标准Snowflake ID共64位,布局如下:
- 1位符号位:固定为0
- 41位时间戳:当前毫秒数减去自定义起始时间
- 10位机器ID:支持1024个节点
- 12位序列号:同一毫秒内的自增计数
二、PHP实现示例
下面给出一个不依赖第三方扩展的PHP雪花算法类,包含时钟回拨保护。
<?php
class Snowflake
{
private $epoch = 1577836800000; // 自定义起始时间 2020-01-01
private $machineId;
private $sequence = 0;
private $lastTimestamp = -1;
public function __construct($machineId)
{
// 机器ID范围 0-1023
$this->machineId = $machineId & 0x3FF;
}
public function nextId()
{
$timestamp = $this->currentMillis();
if ($timestamp < $this->lastTimestamp) {
throw new Exception('时钟回拨异常');
}
if ($timestamp == $this->lastTimestamp) {
$this->sequence = ($this->sequence + 1) & 0xFFF;
if ($this->sequence == 0) {
// 当前毫秒序列用尽,等待下一毫秒
$timestamp = $this->waitNextMillis($this->lastTimestamp);
}
} else {
$this->sequence = 0;
}
$this->lastTimestamp = $timestamp;
return (($timestamp - $this->epoch) << 22)
| ($this->machineId << 12)
| $this->sequence;
}
private function currentMillis()
{
return (int)(microtime(true) * 1000);
}
private function waitNextMillis($last)
{
$ts = $this->currentMillis();
while ($ts <= $last) {
usleep(100);
$ts = $this->currentMillis();
}
return $ts;
}
}
// 使用示例
$flake = new Snowflake(1);
$id = $flake->nextId();
echo $id;
?>
三、在数据库中使用
生成的主键可直接作为表的主键字段,类型建议使用BIGINT UNSIGNED,避免溢出。相比UUID,雪花ID更利于B+树索引顺序写入。
字段定义参考
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | BIGINT UNSIGNED | 雪花算法生成的主键 |
| created_at | datetime | 业务创建时间 |
四、注意事项
机器ID需通过配置或环境变量下发,保证集群内不重复。若服务器时间发生大步回拨,应报警而非继续发号。对时间精度敏感的场景,可结合NTP服务校准。
雪花算法不依赖数据库,因此不会在事务提交前占用数据库连接,适合高并发写入。
五、小结
通过PHP实现Snowflake,可以用很少的代码获得全局唯一、趋势递增的主键生成能力,是分库分表与微服务架构下的实用方案。