发号器
生成唯一id的需求很多,我们经常会用到,不管是单库单表中的唯一,还是分布式的唯一。
SnowFlake 算法
说一下SnowFlake算法,这个算法是一个生成唯一id的算法。
使用的是一个64位的二进制串,把这个串分成了几个部分。
- 符号位 占一个位置 0 为正
- 时间戳位 占41个位置,使用毫秒级时间戳
- 机器位 占10个位置, 可以支持2的10次方-1个机器使用
- 序号位 占12个位置, 同一毫秒内可以生成2的12次方-1个id
但是我们的业务很少用到这个级别的发号器,所以可以把时间改为秒级,下面是我改版后的SnowFlake算法组成:
- 符号位 占一个位置 0 为正
- 时间戳位 占38个位置,使用秒级时间戳
- 机器位 占5个位置, 可以支持2的5次方-1个机器使用
- 业务位 占8个位置, 可以支持2的8次方-1个业务
- 序号位 占12个位置, 同一毫秒内可以生成2的12次方-1个id
我这里面增加了业务位,因为这样可以把每个业务都分开,保证每个业务每个机器每秒内可以生成1024个id。
看一下我们php的实现代码
<?php
class SnowFlake {
const FIRST = 0; //首位 符号位 表示正负0为正 1为负
const TIME_LENGTH = 38; //时间戳位数 二进制的位数
const MACHINE_LENGTH = 5; //机器码位数 二进制的位数
const BUSINESS_LENGTH = 8; //业务位数 二进制的位数
const SEQUENCE_LENGTH = 12; //序列号位数 二进制的位数
private $machineId = 1;
//上一次发号时间
private $oldTime;
private $sequence;
//业务对应的业务id
private $businessArr = [
'order' => 1,
];
function __construct($machineId = 1)
{
if (strlen(decbin($machineId)) > self::MACHINE_LENGTH) {
return '机器id超长!';
}
$this->machineId = $machineId;
//初始化时间戳
$this->oldTime = $this->getTime();
//初始化序号
$this->sequence = 1;
}
/**
* 生成唯一id
* @param string $businessType 业务类型
*/
function generate($businessType) {
$time = $this->getTime();
//比较时间戳
if ($time == $this->oldTime) {
//在同一毫秒内创建,序号递增
if (strlen(decbin($this->sequence)) >= self::SEQUENCE_LENGTH) {
return '到达最大发号个数';
}
++$this->sequence;
} else {
//到达下一个时间,重置序号
$this->sequence = 1;
}
$businessId = $this->getBusinessId($businessType);
//字符位偏移量
$firstShift = self::TIME_LENGTH + self::MACHINE_LENGTH + self::BUSINESS_LENGTH + self::SEQUENCE_LENGTH;
//时间戳偏移量
$timeShift = self::MACHINE_LENGTH + self::BUSINESS_LENGTH + self::SEQUENCE_LENGTH;
//机器位偏移量
$machineShift = self::BUSINESS_LENGTH + self::SEQUENCE_LENGTH;
//业务偏移量
$businessShift = self::SEQUENCE_LENGTH;
$res = self::FIRST << $firstShift | $time << $timeShift | $this->machineId << $machineShift | $businessId << $businessShift | $this->sequence;
//写入时间
$this->oldTime = $time;
return $res;
}
/**
* 获取毫秒级时间戳
*/
function getTime() {
return time();
}
/**
* 获取业务id
*/
function getBusinessId($businessType) {
return $this->businessArr[$businessType];
}
}
最后一步是把每个部分归位,左移相应的偏移量,到达相应的位置,比如时间戳:
1011110100000010111110100010100 << 25 = 10111101000000101111101000101000000000000000000000000000
机器位
1 << 20 = 100000000000000000000
业务位
1 << 12 = 1000000000000
序号位不用左移
最后将这些进行或操作
,二进制的或操作
就是将每一位进行对比,如果都是0则返回0,有一个1就返回1,两个都是1也返回1。
时间戳或上机器位就是这个结果,这也相当于两个加在一起了
10111101000000101111101000101000000000000000000000000000 | 100000000000000000000 = 10111101000000101111101000101000000100000000000000000000
再或上业务位和符号位
10111101000000101111101000101000000100000000000000000000 | 1000000000000 | 1 = 10111101000000101111101000101000000100000010000000000001
最后把每部分或操作
之后就是我们要的最终结果,不管是左移操作还是或操作,实际上都是对变量里面的二进制进行的操作,操作之后会转换成我们看到的十进制消息,也就是53202044035534850
。
在这里面还用到了decbin()
这个函数,这个函数的作用就是将十进制转换成二进制。
我们使用这个发号器生成的唯一id隐含了我们的业务性,也保证了唯一性,如果你的业务很庞大,可以使用毫秒级时间戳或者扩大序号位,都可以根据实际使用情况调整。
下面是这个算法的github地址
https://github.com/Thepatterraining/design-pattern/tree/master/app/Http/Models/SnowFlake