统一ID生成服务
方案前提
ID获取的大前提至少做到单表有序
1、单机本地方案
架构图
原理:利用更新数据库字段方式实现 ID增长与持久化
实现:
1、建表t_id_provider, 包含字段 max_id(当前最大ID值) upd_time(最后一次更新时间)
2、IdService获取id集合使用本地锁,先从数据库取出最大值cMaxId = max_id,然后根据 update max_id = (max_id+取Id个数) where max_id = cMaxId (进行max_id对比主要是防止代码加锁BUG导致重复ID,作为最后一道保障)
优点:
实现简单,代码量小
缺点:
不支持高并发, 且单机模式可用性低,一旦挂了其他服务就无法获取到ID
2、集群本地方案
架构图
原理:工作原理和本地方案基本一致,区别是IdService获取ID集合由本地锁改为基于redis的分布式锁,集群服务用于保障高可用,以及提升不大的并发量(全局锁缘故)
3、单机本地缓存方案
架构图
原理:本地缓存,取一次用多次的方式,服务重启后未使用的ID弃用
实现:
1、建表t_id_provider, 包含字段 max_id(当前最大ID值) upd_time(最后一次更新时间)
2、IdService每次步长增加1万到10万不等,记录cId(当前ID) maxId(当前缓存最大可用ID)
3、其他服务向IdService申请Id时对操作加锁,判断 cId+申请步长<=maxId,若小于等于则修改 cId=cId+申请步长,若不够则从数据库申请足够长度的步长(以万为单位),
4、根据cId和步长大小,返回id集合
缓存法是对单机方式的优化,极大减少与数据库交互,并发数显著上升,但因单机模式并发任然不算太高
4、集群本地号段缓存方案
架构图
原理:号段预发本地缓存方式
实现:
1、建表t_id_provider, 包含字段 max_id(当前最大ID值) upd_time(最后一次更新时间)
2、IdService每次步长增加1万到10万不等,记录cId(当前ID) maxId(当前缓存最大可用ID)
3、IdService启动时向mysql申请号段(redis分布式锁加锁), 例:IdService1 cId=1 maxId=10000;IdService2 cId=10001 maxId=20000; IdService3 cId=20001 maxId=30000;
4、其他服务向IdService申请Id时使用redis分布式锁进行加锁,判断 cId+申请步长<=maxId,若小于等于则修改 cId=cId+申请步长,若不够则从数据库申请足够长度的步长(以万为单位),
关于拓展:若IdService服务需要横向拓展,增加号段提供
故障转移:
5、中间件REDIS方案(将运算交于REDIS)
架构图
原理:使用redis自带的 INCRBY计数命令,实现ID获取
实现:
1、基于redis单线程、高性能的特点,IdService部署集群模式用于保障可用性
2、提前 设置redis硬盘持久化, 设置 key id_generator 永不过期,初始化id_generator 值
3、使用redis自带的计数器功能INCRBY命令实现key的步长增长,并返回增长后的值 returnNum
4、根据 returnNum和步长生成id集合并返回
优点:
基本满足高并发和高可用两点
缺点:
各个服务的id生成依赖于一个key, 集中于同一台REDIS服务器上
改进:
对id_generator 进行拆分,生成多个key,每个key保存一个号段,使用完或者不够用则向id_generator申请,将取id的业务分散在多台redis服务器上
例:hash("com.xxx.app.Test") = 100 再取余100%3=1,key为 id_generator_1,另有 id_generator_0和 id_generator_2,使用hash数据结构两个key分别是key_min key_max 作为区间使用
通过以上计算方式我们可以将原本的 id_generator 分散到了不同的机器上,此时id_generator用以维护当前最大值 而相应的号段则分配在 id_generator_0、id_generator_1、id_generator_2
当其他服务申请id集合时,通过计算得出key(id_generator_0、id_generator_1、id_generator_2), 执行lua脚本,脚本逻辑如下
a、判断 申请ID个数 <= key_max-key_min+1 是否成立, 若成立 则key_min+=申请ID个数 返回key_min
b、若 申请ID个数 <= key_max-key_min+1 不成立,则 计算需要拓展值 num = 申请ID个数+1000 然后 对 id_generator INCRBY(num),key_max+=num, key_min+=申请ID个数 返回key_min