ID生成器针对于日常业务来说,遇到的太频繁了,这里记录一下雪花算法。
雪花算法一共由64位组成:
第1位:未使用
后41位:时间戳
后10位:机器ID
后12位:随机数
在实际的使用过程中,第一位未使用,用0表示。
时间戳范围:假设我们直接使用当前时间戳毫秒级来记录,比如现在是2021-04-04 12:00:00.674,即1617508800674。41位的长度可以涵盖的时间长度(以某一时间为起点,记录时间差)的即(2^41 – 1) / (86400 * 365 * 1000) = 69 年,如果直接将第一位挪出来使用,则可以扩展到139年。
机器ID范围:10位表示,则可以涵盖1023台机器。
后12位:这里的取数是针对一毫秒的并发数据量,我看到有的直接使用随机数来表示,那从严格意义上来讲,这里还是有碰撞的可能。后面我在其他的地方里看到另外一种处理方案:基于信号量和共享内存处理。
这里贴一下核心代码。
// 简版数据组成
// 里面有两种方案,一种是基于数的乘法来得到一个数之后,然后利用字符串拼接的方法
// 另一种直接利用了二进制,通过位移来得到乘法的效果,最后按位或,得到最终的ID
public function createID(): string
{
// 假设一个机器id
$machineId = 1;
// 当前时间
[$uSec, $sec] = explode(' ', microtime());
// 41bit timestamp(毫秒)
$time = floor(($sec + $uSec) * 1000);
# 0bit 未使用
# $suffix = 0 << 63;
// datacenterId 添加数据的时间
# 乘法方案:
# $base = decbin(pow(2, 40) - 1 + $time);
// 位移方案:22位,机器ID的位数10+随机数的位数12
$base = $time << 22;
// workerId 机器ID
# 乘法方案:
# $machineid = decbin(pow(2, 9) - 1 + $machineId);
// 利用位移,机器ID,左移12位
$machineId = $machineId << 12;
// 随机数
$random = $this->getMuxUid($sec);
# $random = decbin(pow(2, 11) - 1 + $random);
$random = decbin($random);
// 拼装所有数据
# 乘法方案,利用拼接
# $base64 = $suffix . $base . $machineid . $random;
// 按位或,高位的每一个区间与低位的每一个区间按位或,相当于加法
$base64 = $base | $machineId | $random;
# 拼接模式下,将二进制转换int
# $base64 = bindec($base64);
return $base64;
}
// 基于共享内存与信号量产生ID
public function getMuxUid(int $timestamp): int
{
$key = ftok(__FILE__, 'p');
// connect一个共享内存,同时设置大小100kb
$shmKey = shm_attach($key, 102400);
// 保存当前累计计数,key为1,调用次数为值
shm_put_var($shmKey, 1, (@shm_get_var($shmKey, 1) ?: 0) + 1);
// 获取一个信号量
$semKey = sem_get($key);
// 请求信号量,这里如果存在堵塞,会有一个等待
if (sem_acquire($semKey)) {
// 判断当前时间戳是否有存储过
if (shm_has_var($shmKey, $timestamp)) {
// 存在则获取出来
$id = shm_get_var($shmKey, $timestamp);
// 同时自增
$id++;
} else {
// 否则初始化ID
$id = 1;
}
// 将当前ID再次放入内存
shm_put_var($shmKey, $timestamp, $id);
// 判断调用次数是否超过1000
if (shm_get_var($shmKey, 1) > 1000) {
// 超过则remove
shm_remove($shmKey);
} else {
// 否则就disconnect
shm_detach($shmKey);
}
return $id;
}
return 0;
}
参考文章