分布式系统——分布式ID方案
分布式ID介绍
在分布式系统中,我们经常需要生成全局唯一的标识符,例如订单号,用户id,消息id等。这些标识符通常被称为分布式id。分布式id有以下几个常见的要求:
- 全局唯一:不同的节点或服务不能生成重复的id。
- 高性能:生成id的速度要快,不能成为系统的瓶颈。
- 低延迟:生成id的过程要尽可能简单,避免多次网络通信或磁盘访问。
- 可排序:生成的id要能按照时间顺序进行排序,方便查询和分析。
- 易扩展:随着系统的增长,能够动态增加或减少生成id的节点或服务,不影响系统的可用性和一致性。
生成策略
数据库自增Id
自增Id是在设计表时将id字段的值设置为自增的形式,这样当插入一行数据时无需指定id会自动根据前一字段的Id值+1进行填充
优点:主键自动增长,不用手工设值、数字型,占用空间小、检索非常有利、有顺序,不会重复。
缺点:并发性能不高,受限于数据库性能、不太适合重构的系统,因为涉及旧数据迁移容易Id冲突,还有外键要考虑、新旧系统上线同时运行,数据涉及双写容易Id冲突。
场景:在互联网公司,对于高并发业务,一般的表主键都是采用数据库自增,即使采用分库分表,也是设置主键自增,原因是:一般表的主键不参与业务处理, 比较常见的设计方式是业务表加一个业务唯一健,而且唯一健不能改变,其他业务表关联这个表的唯一键参与各种逻辑处理。 例如:券表:券Id、券号----活动表:活动Id、活动code-----活动发送记录表: 记录Id、mobile、活动code、券号
UUID生成Id
UUID是通用唯一标识码的缩写,集群全局唯一不会重复,UUID是基于当前时间、计数器、机器的mac地址等数据计算生成的。
优点:本地生成没有网络IO,性能好、全局唯一不重复、使用简单不用引入中间件。
缺点:UUID占用16个字符,空间占用较多,如果是千万级别甚至亿级别特别费空间、不是递增的而且无序、随机写入索引性能下降。
场景:对表空间没太多限制、重构类系统、UUID不作为查询字段的非高并发系统、集群部署的中间件唯一Id。
Redis生成Id
Redis计数器,原子性自增,调用incr、incrBy方法。
优点:并发性能高,有顺序、生成Id格式可以自定义,例如前缀+yyyyMMdd+5位自增。
缺点:使用场景有限、因为自增,数据量容易被猜到,不安全、引入中间件、redis集群部署需要保证高可用成本高。
场景:生成Id要求自增、高性能、适合运营管理后台唯一Id生成,例如活动编号。
雪花算法生成Id
Twitter开源的分布式ID生成算法,主要是由64bit的long型生成的全局ID,引入了时间戳和ID保持自增的属性,组成结构如下:
- 1bit(1:负数,0:正数)
- 41bit(时间戳)
- 5bit(表示机房Id,2^5个机房-32个机房)
- 5bit(表示机器Id,2^5个机器-32个机器)
- 12bit(序列号, 这个是用来记录同一个毫秒内产生的不同id,4096)
优点:不依赖外部组件、性能好、按时间递增
缺点:机房+机器设置不合理高并发下可能会重复(应用启动机房Id、机器ID作为启动参数传递保证唯一)、有时钟回拨的坑 例如:729786672057983180、729786672057983181
场景:互联网高并发系统
百度UidGenerator算法
UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。 UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于k8s环境下实例自动重启、IP漂移等场景。 在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的Uid, 并行化Uid的生产和消费, 同时对CacheLine补齐, 避免RingBuffer带来的伪共享问题. 最终单机QPS可达600万。
场景:互联网高并发系统
美团Leaf算法
全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。 高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+,TP99在1ms内。 接入简单,直接通过公司RPC服务或者HTTP调用即可接入。 支持号段模式和雪花算法: 号段模式依赖于数据库,但是区别于数据库主键自增的模式。假设100为一个号段100,200,300,每取一次可以获得100个Id ,性能显著提高。
场景:互联网高并发系统
实现方案
-
非高并发场景: 提供单独的Id生成restful服务,该服务正常情况不会经常改变,基本做到不发版,保证Id生成底层服务稳定,SLA达到4个9。
-
高并发场景: 提供一个封装好的Id生成SDK,业务应用集成SDK,调用sdk提供的id生成方法,这里通过以下设计保证高可用: . 业务应用启动批量从Id生成restful服务拉取指定数量的Id列表到内存的阻塞队列中(具体的数量根据应用的内存和并发量决定,比如默认为5000)。 . 业务应用获取Id直接从阻塞队列中获取,提高获取的性能。 . sdk中封装一个定时任务,比如5分钟检查一次,如果阻塞队列的Id数量小于5000* 90% = 4500,则重新通过远程服务接口拉取一批, 保证队列存储的Id足够满足业务应用的需求。