【分布式】分布式ID生成-基于数据库方案设计
作者:互联网
目录
1. 需求
- 全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。
- 高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。
- 高并发低延时,远程调用QPS可达5W+,TP99在1ms内。
- 接入简单,通过RPC或者HTTP调用即可接入。
2.方案设计
2.1 数据库设计
数据库中建立如下表,以seq_name为单位,每个seq有独立的id序列。
CREATE TABLE `sequence` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`seq_name` varchar(45) NOT NULL COMMENT '序列名称',
`seq_value` bigint(20) unsigned NOT NULL COMMENT '序列值',
`min_value` bigint(20) NOT NULL COMMENT '最小值',
`max_value` bigint(20) NOT NULL COMMENT '最大值',
`step` bigint(20) NOT NULL COMMENT '步长',
`memo` varchar(64) DEFAULT NULL COMMENT '备注',
`gmt_create` timestamp NULL DEFAULT NULL COMMENT '创建时间',
`gmt_modified` timestamp NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=189 DEFAULT CHARSET=gbk COMMENT='seq生成表';
2.2 代码设计
获取流水号第一反应就是每次都去更新数据库的seq_value,但是由于update的耗时长,并发不了(有写锁),会导致并发低,延迟高,不能满足性能需求。优化方案是预分发的思路:
- 取段:每次从数据库中获取一批SequenceRange流水号,将这个范围缓存起来,并更新数据库seq_value=seq_value+step,这样这一批的流水号就不会被别的机器获取到(无重复要求)。
- 段中取号:每次获取时先从缓存的返回去获取value.getAndIncrement(),如果获取到的id达到范围的max
2.2.1 缺陷:
- 在更新DB的时候会出现耗时尖刺,系统最大耗时取决于更新DB号段的时间。
- 当更新DB号段的时候,如果DB宕机或者发生主从切换,会导致一段时间的服务不可用。
2.2.3 优化方案
异步更新 for 耗时尖刺
号段消费到10%且下个号段未ready,异步线程获取下一个id段,不会影响同步获取id流程的耗时。
双Buffer for 可用性
一次取两个id段buffer,这样DB出现问题能有一个Buffer的号段可以正常对外提供服务,只要DB在一个Buffer的下发的周期内恢复,就不会影响可用性。
2.2.4 新缺陷:
- 号段长度固定的,假如Leaf本来能在DB不可用的情况下,维持10分钟正常工作,如果流量增加10倍就只能维持1分钟了。
- 号段长度设置的过长,导致缓存中的号段迟迟消耗不完,进而导致更新DB的新号段与前一次下发的号段ID跨度过大。
2.2.5 优化方案2
动态调整Step
服务QPS为Q,号段长度为L,号段更新周期为T,那么Q * T = L。
最开始L长度是固定的,导致随着Q的增长,T会越来越小。但是Leaf本质的需求是希望T是固定的。那么如果L可以和Q正相关的话,T就可以趋近一个定值了。所以Leaf每次更新号段的时候,根据上一次更新号段的周期T和号段长度step,来决定下一次的号段长度nextStep:
- T < 15min,nextStep = step * 2
- 15min < T < 30min,nextStep = step
- T > 30min,nextStep = step / 2
至此,满足了号段消耗稳定趋于某个时间区间的需求。当然,面对瞬时流量几十、几百倍的暴增,该种方案仍不能满足可以容忍数据库在一段时间不可用、系统仍能稳定运行的需求。因为本质上来讲,Leaf虽然在DB层做了些容错方案,但是号段方式的ID下发,最终还是需要强依赖DB。
标签:方案设计,COMMENT,seq,DB,号段,2.2,NULL,ID,分布式 来源: https://blog.csdn.net/sarafina527/article/details/105573190