Skip to content

Instantly share code, notes, and snippets.

@cyub
Created February 9, 2020 02:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cyub/841871d4889f80b0ac506f4b608fe194 to your computer and use it in GitHub Desktop.
Save cyub/841871d4889f80b0ac506f4b608fe194 to your computer and use it in GitHub Desktop.
<?php
class SnowflakeIdWorker
{
// 开始时间戳(时间是2020-01-23 21:54:56,单位毫秒)
// 此时间戳支持的时间约到2020+69
private $twepoch = 1579816496000;
// 机器id所占的位数
const WORKERID_BITS = 5;
// 数据中心标识id所占的位数
const DATACENTERID_BITS = 5;
// 序列在id中占的位数
const SEQUENCE_BITS = 12;
// 机器ID向左移12位
private $workerIdShift = self::SEQUENCE_BITS;
// 数据中心标识id向左移17位(12+5)
private $datacenterIdShift = self::SEQUENCE_BITS + self::WORKERID_BITS;
// 时间截向左移22位(5+5+12)
private $timestampLeftShift = self::SEQUENCE_BITS + self::WORKERID_BITS + self::SEQUENCE_BITS;
// 支持的最大机器id,结果是31
// 通过移位算法计算出n位二进制数所能表示的最大十进制数
private $maxWorkerId = -1 ^ (-1 << self::WORKERID_BITS);
// 支持的最大数据标识id,结果是31
private $maxDatacenterId = -1 ^ (-1 << self::DATACENTERID_BITS);
// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
private $sequenceMask = -1 ^ (-1 << self::SEQUENCE_BITS);
// 工作机器ID(0~31)
private $workerId;
// 数据中心ID(0~31)
private $datacenterId;
// 毫秒内序列(0~4095)
// php非常驻内存运行模式下,sequence应该从第三方存储读写
private $sequence = 0;
// 上次生成ID的时间截
// php非常驻内存运行模式下,lastTimestamp应该从第三方存储读写
private $lastTimestamp = -1;
public function __construct($workerId, $datacenterId)
{
if ($workerId > $this->maxWorkerId || $workerId < 0) {
throw new IllegalArgumentException(sprintf("worker Id can't be greater than %d or less than 0", $maxWorkerId));
}
if ($datacenterId > $this->maxDatacenterId || $datacenterId < 0) {
throw new IllegalArgumentException(sprintf("datacenter Id can't be greater than %d or less than 0", $maxDatacenterId));
}
$this->workerId = $workerId;
$this->datacenterId = $datacenterId;
}
public function nextId()
{
$timestamp = $this->timeGen();
// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if ($timestamp < $this->lastTimestamp) {
throw new RuntimeException(
sprintf("Clock moved backwards. Refusing to generate id for %d milliseconds", $this->lastTimestamp - $timestamp));
}
// 如果是同一时间生成的,则进行毫秒内序列
if ($this->lastTimestamp == $timestamp) {
$this->sequence = ($this->sequence + 1) & $this->sequenceMask;
// 毫秒内序列溢出
if ($this->sequence == 0) {
// 阻塞到下一个毫秒,获得新的时间戳
$timestamp = $this->tilNextMillis($this->lastTimestamp);
}
} else { // 时间戳改变,毫秒内序列重置
$this->sequence = 0;
}
// 记录上次生成ID的时间截
$this->lastTimestamp = $timestamp;
// 移位并通过或运算拼到一起组成64位的ID
return (($timestamp - $this->twepoch) << $this->timestampLeftShift) //
| ($this->datacenterId << $this->datacenterIdShift) //
| ($this->workerId << $this->workerIdShift) //
| $this->sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected function tilNextMillis($lastTimestamp)
{
$timestamp = $this->timeGen();
while ($timestamp <= $lastTimestamp) {
$timestamp = $this->timeGen();
}
return $timestamp;
}
protected function timeGen()
{
return intval(microtime(true) * 1000);
}
}
function test()
{
$idWorker = new SnowflakeIdWorker(0, 0);
for ($i = 0; $i < 100000; $i++) {
$id = $idWorker->nextId();
printf("%d => %d\n", $i, ($id));
}
}
test();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment