发号器
生成唯一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的实现代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| <?php
class SnowFlake {
const FIRST = 0;
const TIME_LENGTH = 38;
const MACHINE_LENGTH = 5;
const BUSINESS_LENGTH = 8;
const SEQUENCE_LENGTH = 12;
private $machineId = 1;
private $oldTime;
private $sequence;
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; }
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(); }
function getBusinessId($businessType) { return $this->businessArr[$businessType]; } }
|
最后一步是把每个部分归位,左移相应的偏移量,到达相应的位置,比如时间戳:
1
| 1011110100000010111110100010100 << 25 = 10111101000000101111101000101000000000000000000000000000
|
机器位
1 2
| 1 << 20 = 100000000000000000000
|
业务位
序号位不用左移
最后将这些进行或操作
,二进制的或操作
就是将每一位进行对比,如果都是0则返回0,有一个1就返回1,两个都是1也返回1。
1 2 3 4 5 6 7
| 时间戳或上机器位就是这个结果,这也相当于两个加在一起了 10111101000000101111101000101000000000000000000000000000 | 100000000000000000000 = 10111101000000101111101000101000000100000000000000000000
再或上业务位和符号位
10111101000000101111101000101000000100000000000000000000 | 1000000000000 | 1 = 10111101000000101111101000101000000100000010000000000001
|
最后把每部分或操作
之后就是我们要的最终结果,不管是左移操作还是或操作,实际上都是对变量里面的二进制进行的操作,操作之后会转换成我们看到的十进制消息,也就是53202044035534850
。
在这里面还用到了decbin()
这个函数,这个函数的作用就是将十进制转换成二进制。
我们使用这个发号器生成的唯一id隐含了我们的业务性,也保证了唯一性,如果你的业务很庞大,可以使用毫秒级时间戳或者扩大序号位,都可以根据实际使用情况调整。
下面是这个算法的github地址
https://github.com/Thepatterraining/design-pattern/tree/master/app/Http/Models/SnowFlake