分布式系统中不同机器产生的id必须不同。可以使用snowflake保证id唯一。
snowflake原理
算法核心: 把时间戳、工作机器Id、序列号组合在一起。
除了最高位bit标记不可用之外,其余三组bit占位均可浮动,看具体的业务需求而定。默认情况下41bit的时间戳可以支持该算法使用到2089年,10bit的工作机器id可以支持1024台机器,序列号支持1毫秒产生4096个自增序列id。
ps:提供一个github上用java实现的:https://github.com/beyondfengyu/SnowFlake
时间戳
时间戳的粒度是毫秒级,代码如下:
uint64_t generateStamp()
{
timeval tv;
gettimeofday(&tv, 0);
return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
}
默认情况下有41bit可以供使用,那么一共有T(1L << 41)毫秒供你使用分配,可使用年份=T/(3600*24*365*1000)=69.7年。
其中1L << 41 = 1 00000000 00000000 00000000 00000000 00000000(二级制)=2199023255552(十进制)。
工作机器id
机器级可以使用MAC地址来唯一标识工作机器,工作进程级的话可以使用IP+Path来区分工作进程。如果工作机器较少,可以写到配置文件中配置,但如果机器过多维护配置文件就有点力不从心了。
这里的解决方案需要一个工作id分配的进程,可以自己编写一个简单的进程来记录分配id,或者使用mysql的auto_increment机制。
工作进程与工作id分配器只是在工作进程启动的时候交互一次,然后工作进程可以自行将分配的id数据落文件,下一次启动直接读取文件里的id使用。
ps:工作机器id的10bit可以进一步拆分,根据自己需要拆分即可。比如5个bit标记进程id,5个bit表级线程id etc...
序列号
一些列的自增id(多线程使用atomic),1毫秒内如果需要给多条消息分配id,若同一毫秒序列号4095个用完了(2^12=4096),则“等待至下一毫秒”。
uint64_t waitNextMs(uint64_t lastStamp)
{
uint64_t cur = 0;
do {
cur = generateStamp();
} while (cur <= lastStamp);
return cur;
}
在多线程的环境下,序列号使用atomic可以在代码实现上有效减少锁的密度。
参考资料:https://github.com/twitter/snowflake
由于snowflake中最难实践的是工作机器id,原始的snowflake算法需要人工去为每台机器去指定一个机器id,并配置在某个地方从而让snowflake从此处获取机器id。
但是在大厂里,机器是很多的,人力成本太大且容易出错,所以大厂对snowflake进行了改造。
常见的两种snowflake实现。
-
美团的Leaf
There are no two identical leaves in the world.(世界上没有两片完全相同的树叶。)
一个分布式ID生成框架,支持号段模式(不在本文介绍),支持snowflake模式。
与原始的snowflake算法的不同点在于机器id(workid)的生成,Leaf的workdId是基于ZooKeeper的顺序id生成。每个应用在使用Leaf-snowflake时,在启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。
附Leaf github地址:https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md
-
百度(uid-generator)
在uid-generator中用户可以自己去定义workId的生成策略,默认提供的策略是:应用启动时由数据库分配。说的简单一点就是:应用在启动时会往数据库表(uid-generator需要新增一个WORKER_NODE表)中去插入一条数据,数据插入成功后返回的该数据对应的自增唯一id就是该机器的workId,而数据由host,port组成。
-
sign(1bit)
固定1bit符号标识,即生成的UID为正数。 -
delta seconds (28 bits)
当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年 -
worker id (22 bits)
机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。 -
sequence (13 bits)
每秒下的并发序列,13 bits可支持每秒8192个并发。
在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。
附uid-genertor github地址:https://github.com/baidu/uid-generator
本文完
勤助手-教育机构好助手。一款专注中小机构教学管理的软件。
注册可免费使用。