唯一ID的用途
- 作为业务数据的唯一标识,用该ID来进行分库分表
- 使用绝对自增的ID作为绝对时钟的模拟
业务系统对ID号的要求
概括下来,那业务系统对ID号的要求有哪些呢?
全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
趋势递增:可以作为排序字段
- 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
- 信息安全(防逆向):如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
- 一般会将该ID用作索引,为性能考虑考虑的话,一般使用64位整数。如果允许用字符串的话,用上面的UUID即可。
- 实际项目中,我们更有可能使用53位整数作为唯一ID(主要考虑是JS等语言或者JSON协议等对int64的支持不好,其限制在于仅支持float)
常见实现方案
借助数据库的主键ID
优点:简单,绝对自增
不足:该方案存在单点故障风险;另外,数据库能支撑的吞吐比较低
优化:考虑多台Master,设置不同的步长
借助Redis的incr
优点:性能好、绝对自增
不足:redis的定位是缓存,数据有丢失的可能。
雪花算法
实现思路:时间戳 + 机器ID + 自增ID(随机,而不要从0开始)
优点:简单、性能好、趋势递增
不足:依赖机器的时钟,如果服务器时钟回拨,会导致重复ID生成。
时钟回拨
可以考虑通过历史时间(当自增ID发生回环时,进行+1)、扩展位(当检测到本次时间相比之前变小,则扩展位++)
借助Zookeeper
优点:唯一性有保证,上面雪花算法中的机器ID便可以考虑使用该方案来获取。
不足:吞吐量低,仅32位
UUID
本质:去中心的生成方法本身没法保证唯一性。一般降到实际可忽略的重复概率就能用了。分布式系统之所以难,很重要的原因之一是“没有一个全局时钟,难以保证绝对的时序”,要想保证绝对的时序,还是只能使用单点服务,用本地时钟保证“绝对时序”。
Leaf算法
实现思路:号段模式,由数据库先来负责分配号段,再由专门的分发服务来负责自增生成唯一ID
优点:分布式,无单点;性能好
不足:实现略复杂
微信的自增ID生成
给我们的一些启发:
逻辑服务层:负责加载max_seq=>cur_seq,每次分配新的时,cur_seq++;如果超出,则持久化max_seq,从而减少了对store的读写
store层:负责存储每一个用户的max_seq,但是如果每一个用户都存储一个max_seq的话,使用的量有点大,可以考虑一个uid范围内的公用一个max_seqno
store的持久化:NRW策略(W +R > N)
set化部署,号段隔离
如何确保ID不回撤 等价于 如何确保一个号段的请求,总是落到同一个逻辑服务上。
路由表:版本号
第三方仲裁去更新路由表,逻辑服务去访问store层时去
参考:https://www.infoq.cn/article/wechat-serial-number-generator-architecture