如何设计一款“高可用高性能”的发号器?
背景
在分布式场景中,很多地方需要生成全局唯一的 id,如数据库分库分表后需要用唯一 id 代替单机版本的自增 id。发号器的基本要求是
全局唯一,无论如何都不能重复
某些场景下还要求单调递增,如排序需求等。
网上有很多介绍发号器的文章,比如美团的《Leaf——美团点评分布式 ID 生成系统》,有赞的《如何做一个靠谱的发号器》等。本文聚焦高可用,高性能
高可用:不会因为系统故障导致服务不可用或发号重复
高性能:发号器通常是一个非常高并发的系统,性能足够的同时也可以水平扩容
在基本的要求下,常见的解决方案有哪些?他们是否是高可用
,高性能
的呢?
snowflake 方案
snowflake
采用 41 位时间戳加 10 位机器 id 加 12 位序列号的方式生成,序列号在单一进程内可使用 AtomicLong 来生成,10 位机器号可支持 1024 台机器
该算法优点是:
算法简单,易于实现,不依赖任何第三方系统,性能非常高;
集群无状态,可随意扩缩容,可认为是高可用系统。
缺点是:
高 10 位的时间戳和低位自增序列号可保证单调增,但机器号无法保证,如机器号为 2 在某一时刻先生成 id,机器号为 1 在同一时刻后生成 id,则不能保证单调性;
依赖时间戳,如果时钟回拨,可能会生成重复的 id。
综合来看,snowflake
方案符合基本要求,性能非常高,但其存在时钟回拨问题,因而是高性能
但不是高可用
的方案。
基于数据库方案
利用数据库的自增id
特性实现,该方案优点:
实现比较简单,只依赖数据库;
没有时钟回拨问题;
生成的 id 单调递增。
缺点:
性能被数据库限制,数据库的单机写入性能有限,也无法扩缩容;
数据库存在单点故障,如果是主从架构,取决于是
异步复制
、半同步复制
、全同步
复制配置,只有全同步复制
才能保证可用性,其他配置无法保证主从数据的一致性,一旦主库发生故障,主库的变更还未应用到从库,则主从切换后可能会存在发号重复的问题。
同理,这里的数据库也可以替换为redis
,利用 redis 的incr
来实现,但 redis 只有异步复制
,更加无法保证数据一致性。
综合来看,基于数据库的方案如果不开启全同步复制,就不是高可用方案,如果开启全同步复制,则性能一定会有问题(就算不开启全同步复制也会有性能问题)。
基于数据库的号段方案
本方案是对数据库方案的一种性能优化,每次从数据库取回的不是一个 id,而是一个号段,在单独进程内通过锁保证每次发放一个唯一的 id,甚至可以在系统快要发放完号码时异步地去获取下一个号段,该方案性能明显高于数据库方案,但也失去了 id 单调递增的特性,如果开启全同步复制,则可认为是一个高可用
的方案,通过调大号段的长度,可以达到高性能
的要求。
基于多主库的数据库方案
本方案也是对数据库号方案的一种优化,采用多台数据库
,假设 3 台主库设置自增 id 起始分别为 1,2,3,步长都设置为 3,这样 1 号数据库获取的自增 id 为 1,4,7...,2 号数据库获取的自增 id 为 2,5,8...,3 号数据库获取的自增 id 为 3,6,9...,他们永远不会重复。系统每次取号段时采取轮询策略,如果有一台数据库获取失败,则继续从下一个数据库获取。该方案解决了数据库的高可用问题,个别数据库宕机不影响系统正常运行。高性能也是通过号段的方式来解决,如果运行过程中对数据库进行水平扩容则比较困难。
基于一致性协议的方案
上面数据库的高可用问题主要来源于主从数据不一致
,如果使用一致性协议来保证数据的一致性,就可以解决高可用
问题,目前最常使用的raft
算法,可以保证数据复制到半数以上
机器。在我们每获取一个号段后,已发出的号段都被持久化到半数以上机器,并且最终复制到所有机器,当 master 挂掉后 raft 重新选举。有赞的《如何做一个靠谱的发号器》
就是采取这种办法,他们使用的组件是etcd
。甚至可以基于开源的raft
库来自己实现一个发号器,如果要自己来实现一个靠谱
的raft
协议,还是比较困难的,开源的raft
库可选用蚂蚁开源的SOFAJRaft
。
总结
发号器的
高性能
主要依靠号段
的方式来解决;发号器的
高可用
可以依靠数据库
的高可用、多主库、一致性协议
来实现。
版权声明: 本文为 InfoQ 作者【小楼】的原创文章。
原文链接:【http://xie.infoq.cn/article/cf31eba825e6439ca0f94ebcb】。文章转载请联系作者。
评论