下面先解释一下各个区间的浸染。

符号位:用于区分正负数。
1为负数,0为整数。
一样平常不须要负数,以是值固定为0。
韶光戳:一共预留41bit保存毫秒级韶光戳。
由于毫秒级韶光戳长度是13位:41位二进制最大值(T)是:241−1=2199023255551241−1=2199023255551 , 刚好13位。
可以表示的年份 = T / (3600 24 365 1000) = 69.7年。
换算成Unix韶光也便是可以表示到:2039-09-07 23:47:35:

大家会以为这个韶光不足用啊,没紧要,后面会讲如何优化。

事情机器:预留了10bit保存机器ID。
只要机器ID不一样,每毫秒天生的ID是不一样的。
一共可以支持多少台机器同时天生ID呢? 答案是 1023 台(210−1210−1)。
如果事情机器比较少,可以利用配置文件来设置这个id,或者利用随机数。
如果机器过多就得单独实现一共事情机器ID分配器了,比如利用redis自增,或者利用Mysql auto_increment机制也可以达到效果。
序列号:序列号一共是12bit,为了处理在同一机器同一毫秒内须要给多条分配id的情形,一共可以产生4095个序列号(0~4095, 212−1212−1)。

综上:同一台机器1毫秒内可产生4095个ID,全部机器1毫秒内可产生 4095 1023 个ID。
由于全是在各个机器本地天生,效率非常高。

id取模phpTwitterSnowflake自增ID算法 Webpack

大略实现

下面是一个大略实现:仅有韶光戳,机器位为0,序列号为0:

#include int main(){ long long id; id = 1572057648000 << 22; //相称于 id = 1572057648000 << 22 | 0 << 12 | 0; printf(\"大众id=%lld\n\"大众, id); return 0;}

输出:

id=6593687681236992000

代码实现紧张用到了左移和或位运算(或运算),各个措辞类似。
上面的实现输出的结果是一个19位长度的整数。

优化

1、韶光戳优化

如果韶光戳取当前毫秒级韶光戳,那么只能表示到2039年,远远不足。
我们创造,1970到当前韶光这个区间实在是永久都不会用了,那么,为何不该用偏移量呢?也便是韶光戳部分不直接取当前毫秒级韶光戳,而是在此根本上减去一个过去韶光:

id = (1572057648000 - 1569859200000) << 22;

输出:

id=9220959240192000

上面代码中,第一个韶光戳是当前毫秒级韶光戳,第二个则是一个过去韶光戳(1569859200000表示2019-10-01 00:00:00)。
这样我们可以表示的年大概是 当前年份(例如2019) + 69 = 2088 年,很长一段韶光内都够用。

2、序列号

序列号默认取0,如果已经利用了则自增。
若自增到4096,也便是同一毫秒内的序列号用完了,怎么办呢?须要等待至下一毫秒。
部分代码示例:

//同一毫秒并发调用if (ts == (iw.last_time_stamp)) { //序列号自增 iw.sequence = (iw.sequence+1) & MASK_SEQUENCE; //序列号自增到最大值4096,4095 & 4096 = 0 if (iw.sequence == 0) { //等待至下一毫秒 ts = time_re_gen(ts); }} else { //同一毫秒没有重复的 iw.last_time_stamp = ts;}算法变种

1、53bits版本:由于js只支持53位bit的数值

0 32 51 53+-----------+------+------+|0|time(32) |workid(8) |seq(12) |+-----------+------+------+

2、其它版本

我们也可以根据自己的业务需求,将不同区间的bit位进行调度。
机器位和序列号ID并不是必须的,可以合并。
或者拆分出更多的区间表示更多的意义。
例如订单号:

0 41 47 59 64+-----------+------+------+------+------+|0|time(41) |workid(6) |seq(12) | uid(4)+-----------+------+------+------+------+

我们对订单分16个(2^4)表,每次将 uid & 0xF(也便是 uid & 15)的结果放到后四位,这样往后根据uid查订单的时候,uid mod 16 就能得到数据在哪个分表;同时根据订单ID本身也能找到对应的分表。
示例:

php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (1820 & 15);6593741087309889548php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15);6593741087309889539

验证测试:

php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15);6593741087309889539php > echo 6593741087309889548 % 16;12php > echo 1820 % 16;12php > echo 6593741087309889539 % 16;3php > echo 5177331 % 16;3

从上面的结果可以看出来,uid、订单号都能定位到相同的分表。

对一个2的n次幂的数num取模(2^n),实质便是num对应二进制的末端n个bit的和取模。

代码实现

参考网上其它措辞的版本,自己写了C和PHP版本的:

1、snowflake-c/snowflake.c at master · 52fhy/snowflake-c

https://github.com/52fhy/snowflake-c

2、52fhy/IDWork: Twitter的 Snowflake的PHP版

https://github.com/52fhy/IDWork

3、github上其它措辞版本:

go措辞版本:已用于生产环境,稳定:https://github.com/bwmarrin/snowflakephp c扩展版:未利用过fgy58963/php_snowflake: https://github.com/fgy58963/php_snowflake参考

1、Twitter-Snowflake,64位自增ID算法详解 - 漫漫路

https://www.lanindex.com/twitter-snowflake%EF%BC%8C64%E4%BD%8D%E8%87%AA%E5%A2%9Eid%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3/

2、多key业务,数据库水平切分架构一次搞定

https://mp.weixin.qq.com/s/PCzRAZa9n4aJwHOX-kAhtA