Twitter 早期用 MySQL 存储数据,随着用户的增加,单一的 MySQL 实例无法承受海量的数据,后来团队就研究如何产生完美的自增ID,以知足两个基本的要求:php
Twitter-Snowflake算法就是在这样的背景下产生的。git
Twitter 解决这两个问题的方案很是简单高效:每个 ID 都是 64 位数字,由时间戳、工做机器节点和序列号组成, ID是由当前所在的机器节点生成的。如图:github
下面先说明一下各个区间的做用。redis
你们会以为这个时间不够用啊,不要紧,后面会讲如何优化。算法
若是工做机器比较少,可使用配置文件来设置这个id,或者使用随机数。若是机器过多就得单独实现一共工做机器ID分配器了,好比使用redis自增,或者利用Mysql auto_increment机制也能够达到效果。sql
综上:同一台机器1毫秒内可产生4095个ID,所有机器1毫秒内可产生 4095 * 1023 个ID。因为全是在各个机器本地生成,效率很是高。数据库
下面是一个简单实现:仅有时间戳,机器位为0,序列号为0:架构
#include <stdio.h> int main() { long long id; id = 1572057648000 << 22; //至关于 id = 1572057648000 << 22 | 0 << 12 | 0; printf("id=%lld\n", id); return 0; }
输出:并发
id=6593687681236992000
代码实现主要用到了左移和或位运算(或运算),各个语言相似。上面的实现输出的结果是一个19位长度的整数。分布式
一、时间戳优化
若是时间戳取当前毫秒级时间戳,那么只能表示到2039年,远远不够。咱们发现,1970到当前时间这个区间实际上是永远都不会用了,那么,为什么不使用偏移量呢?也就是时间戳部分不直接取当前毫秒级时间戳,而是在此基础上减去一个过去时间:
id = (1572057648000 - 1569859200000) << 22;
输出:
id=9220959240192000
上面代码中,第一个时间戳是当前毫秒级时间戳,第二个则是一个过去时间戳(1569859200000表示2019-10-01 00:00:00)。这样咱们能够表示的年大概是 当前年份(例如2019) + 69
= 2088 年,很长一段时间内都够用。
二、序列号
序列号默认取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; }
一、53bits版本:由于js只支持53位bit的数值
* 0 32 51 53 +-----------+------+------+ |0|time(32) |workid(8) |seq(12) | +-----------+------+------+
二、其它版本
咱们也能够根据本身的业务需求,将不一样区间的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); 6593741087309889548 php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15); 6593741087309889539
验证测试:
php > echo 1572070381000 << 22 | 1 << 16 | 0 << 4 | (5177331 & 15); 6593741087309889539 php > echo 6593741087309889548 % 16; 12 php > echo 1820 % 16; 12 php > echo 6593741087309889539 % 16; 3 php > echo 5177331 % 16; 3
从上面的结果能够看出来,uid、订单号都能定位到相同的分表。
对一个2的n次幂的数num取模(2^n),本质就是num对应二进制的末尾n个bit的和取模。
参考网上其它语言的版本,本身写了C和PHP版本的:
github上其它版本:
fgy58963/php_snowflake: 推特分布式主键生成算法的php扩展
https://github.com/fgy58963/p...
一、Twitter-Snowflake,64位自增ID算法详解 - 漫漫路
https://www.lanindex.com/twit...
二、多key业务,数据库水平切分架构一次搞定
https://mp.weixin.qq.com/s/PC...
本文首发于公众号"飞鸿影的博客(fhyblog)",欢迎关注。博客地址: https://52fhy.cnblogs.com 。
(本文完)