PHP分布式ID生成策略与实现
2026/6/1 20:50:57 网站建设 项目流程

PHP分布式ID生成策略与实现

在分布式系统中,生成全局唯一的ID是一个常见需求。今天说说PHP中实现分布式ID的各种方法。

自增ID是最简单的方式,但在分布式环境下不能使用。

```php
// Redis自增ID
class RedisIdGenerator
{
private Redis $redis;
private string $prefix;
private int $batchSize;

public function __construct(Redis $redis, string $prefix = 'id:', int $batchSize = 100)
{
$this->redis = $redis;
$this->prefix = $prefix;
$this->batchSize = $batchSize;
}

public function next(string $key): int
{
return $this->redis->incr("{$this->prefix}{$key}");
}

public function nextBatch(string $key): array
{
$start = $this->redis->incrBy("{$this->prefix}{$key}", $this->batchSize);
$ids = [];
for ($i = 0; $i < $this->batchSize; $i++) {
$ids[] = $start - $i;
}
return $ids;
}
}
?>

雪花算法(Snowflake)是分布式ID生成的标准方案。它由时间戳、机器ID和序列号组成。

```php
class SnowflakeIdGenerator
{
private int $workerId;
private int $datacenterId;
private int $sequence = 0;
private int $lastTimestamp = -1;

// 位偏移
private int $workerIdBits = 5;
private int $datacenterIdBits = 5;
private int $sequenceBits = 12;

// 最大值
private int $maxWorkerId;
private int $maxDatacenterId;

// 位偏移量
private int $workerIdShift;
private int $datacenterIdShift;
private int $timestampShift;

// 起始时间戳(2024-01-01)
private int $epoch = 1704067200;

public function __construct(int $workerId = 0, int $datacenterId = 0)
{
$this->maxWorkerId = -1 ^ (-1 << $this->workerIdBits);
$this->maxDatacenterId = -1 ^ (-1 << $this->datacenterIdBits);

if ($workerId > $this->maxWorkerId || $workerId < 0) {
throw new InvalidArgumentException("Worker ID {$workerId} 超出范围 (0-{$this->maxWorkerId})");
}
if ($datacenterId > $this->maxDatacenterId || $datacenterId < 0) {
throw new InvalidArgumentException("Datacenter ID {$datacenterId} 超出范围 (0-{$this->maxDatacenterId})");
}

$this->workerId = $workerId;
$this->datacenterId = $datacenterId;

$this->workerIdShift = $this->sequenceBits;
$this->datacenterIdShift = $this->workerIdShift + $this->workerIdBits;
$this->timestampShift = $this->datacenterIdShift + $this->datacenterIdBits;
}

public function nextId(): int
{
$timestamp = $this->getTimestamp();

if ($timestamp < $this->lastTimestamp) {
throw new RuntimeException("时钟回退,拒绝生成ID");
}

if ($timestamp === $this->lastTimestamp) {
$this->sequence = ($this->sequence + 1) & (-1 ^ (-1 << $this->sequenceBits));
if ($this->sequence === 0) {
// 当前毫秒序列号用完,等待下一毫秒
$timestamp = $this->waitNextMillis($timestamp);
}
} else {
$this->sequence = 0;
}

$this->lastTimestamp = $timestamp;

return (($timestamp - $this->epoch) << $this->timestampShift)
| ($this->datacenterId << $this->datacenterIdShift)
| ($this->workerId << $this->workerIdShift)
| $this->sequence;
}

public function parseId(int $id): array
{
$timestamp = ($id >> $this->timestampShift) + $this->epoch;
$datacenterId = ($id >> $this->datacenterIdShift) & $this->maxDatacenterId;
$workerId = ($id >> $this->workerIdShift) & $this->maxWorkerId;
$sequence = $id & (-1 ^ (-1 << $this->sequenceBits));

return [
'id' => $id,
'timestamp' => $timestamp,
'datetime' => date('Y-m-d H:i:s', $timestamp),
'datacenter_id' => $datacenterId,
'worker_id' => $workerId,
'sequence' => $sequence,
];
}

private function getTimestamp(): int
{
return (int)(microtime(true) * 1000);
}

private function waitNextMillis(int $currentTimestamp): int
{
while ($currentTimestamp <= $this->lastTimestamp) {
usleep(100);
$currentTimestamp = $this->getTimestamp();
}
return $currentTimestamp;
}
}

$generator = new SnowflakeIdGenerator(1, 1);

$ids = [];
for ($i = 0; $i < 10; $i++) {
$ids[] = $generator->nextId();
}

echo "生成的ID:\n";
foreach ($ids as $id) {
$info = $generator->parseId($id);
echo " {$id} -> {$info['datetime']} (DC:{$info['datacenter_id']}, W:{$info['worker_id']}, Seq:{$info['sequence']})\n";
}
?>
```

ULID是另一种分布式ID生成方案,比UUID更短,且可以排序。

```php
class UlidGenerator
{
private static string $encoding = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';

public static function generate(): string
{
$time = microtime(true);
$timestamp = (int)($time * 1000);
$random = random_bytes(10);

// 时间戳部分(10字符)
$timeChars = '';
for ($i = 9; $i >= 0; $i--) {
$timeChars = self::$encoding[$timestamp % 32] . $timeChars;
$timestamp = (int)($timestamp / 32);
}

// 随机部分(16字符)
$randomChars = '';
$rand = unpack('C*', $random);
foreach ($rand as $byte) {
$randomChars .= self::$encoding[$byte % 32];
}

return strtolower($timeChars . $randomChars);
}

public static function extractTime(string $ulid): float
{
$timeChars = substr($ulid, 0, 10);
$timestamp = 0;

for ($i = 0; $i < 10; $i++) {
$pos = strpos(self::$encoding, strtoupper($timeChars[$i]));
if ($pos === false) continue;
$timestamp = $timestamp * 32 + $pos;
}

return $timestamp / 1000;
}
}

$ulids = [];
for ($i = 0; $i < 5; $i++) {
$ulids[] = UlidGenerator::generate();
}

echo "ULID:\n";
foreach ($ulids as $ulid) {
$time = UlidGenerator::extractTime($ulid);
echo " {$ulid} -> " . date('Y-m-d H:i:s', (int)$time) . "\n";
}

sort($ulids);
echo "\n排序后:\n";
foreach ($ulids as $ulid) {
echo " {$ulid}\n";
}
?>
```

UUID作为最简单的方案,但性能和可读性不如雪花算法和ULID。

分布式ID生成方案的选择取决于具体需求。雪花算法性能高、可排序,但依赖时钟。ULID可排序、纯随机,但不含机器信息。UUID生成简单,但不具备业务意义。Redis自增适合小规模应用。选择合适的方案能避免很多分布式环境下的ID冲突问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询