标签:保存 需求 atomic 生成 集群 分布式ID update 也有 unique
分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表。因为数据量巨大一张表无法承接,就会对其进行分库分表。
但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID
的生成问题。
ID
唯一;递增 | 趋势递增 |
---|---|
第一次生成的ID为12,下一次生成的ID是13,再下一次生成的ID是14。 | 什么是?如:在一段时间内,生成的ID是递增的趋势。如:再一段时间内生成的ID在【0,1000】之间,过段时间生成的ID在【1000,2000】之间。但在【0-1000】区间内的时候,ID生成有可能第一次是12,第二次是10,第三次是14。 |
UUID
全称:Universally Unique Identifier
。标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12
的36个字符,示例:9628f6e9-70ca-45aa-9f7c-77afe0d26e05
。
ID
,所以迁移数据容易。ID
是无序的,无法保证趋势递增;UUID
的字符串存储,查询效率慢;ID
本身无业务含义,不可读。也有在线生成
UUID
的网站,如果你的项目上用到了UUID
,可以用来生成临时的测试数据。https://www.uuidgenerator.net/
利用了MySQL
的主键自增auto_increment
,默认每次ID
加1
。
优点:
ID
递增;MySQL
挂了,就没法生成ID
了;这个方案就是解决MySQL
的单点问题,在auto_increment
基本上面,设置step
步长
如上,每台的初始值分别为1
,2
,3
...N
,步长为N
(这个案例步长为4
)
单机:Redis
的incr
函数在单机上是原子操作,可以保证唯一且递增。
集群:单机Redis
可能无法支撑高并发。集群情况下,可以使用步长的方式。比如有5个Redis
节点组成的集群,它们生成的ID
分别为:
A: 1,6,11,16,21
B: 2,7,12,17,22
C: 3,8,13,18,23
D: 4,9,14,19,24
E: 5,10,15,20,25
Redis
进行请求。数据库的自增主键的特性,可以实现分布式ID,适合做userId,正好符合如何永不迁移数据和避免热点? 但这个方案有严重的问题:
因为我们每次获取ID的时候,都要去数据库请求一次。那我们可以不可以不要每次去取?
可以请求数据库得到ID的时候,可设计成获得的ID是一个ID区间段。
ID
规则表含义:id
表示为主键,无业务含义;biz_tag
为了表示业务,因为整体系统中会有很多业务需要生成ID
,这样可以共用一张表维护;max_id
表示现在整体系统中已经分配的最大ID
;desc
描述;update_time
表示每次取的ID
时间;ID
;会请求【生成ID
服务(是独立的应用)】的接口;ID
服务】会去查询数据库,找到user_tag
的id
,现在的max_id
为0
,step=1000
;ID
服务】把max_id
和step
返回给【用户服务】;并且把max_id
更新为max_id = max_id + step
,即更新为1000
;max_id=0
,step=1000
;ID=【max_id + 1,max_id+step】
区间的ID
,即为【1,1000】
;jvm
中;ID
的时候,在区间【1,1000】
中依次获取ID
,可采用AtomicLong
中的getAndIncrement
方法;如果把区间的值用完了,再去请求【生产ID
服务】接口,获取到max_id
为1000
,即可以用【max_id + 1,max_id+step】
区间的ID
,即为【1001,2000】
。
max_id
的起点,和step
步长,非常方便扩容;也解决了数据库压力的问题,因为在一段区间内,是在jvm
内存中获取的,而不需要每次请求数据库。即使数据库宕机了,系统也不受影响,ID
还能维持一段时间。
以上方案中,如果是多个用户服务,同时获取ID
,同时去请求【ID服务】,在获取max_id
的时候会存在并发问题。如:
用户服务
A
,取到的max_id=1000
;用户服务B
取到的也是max_id=1000
,那就出现了问题,ID
重复了。
解决方案是:加分布式锁,保证同一时刻只有一个用户服务获取max_id
。
因为竞争问题,所有只有一个用户服务去操作数据库,其他二个会被阻塞。出现的现象就是一会儿突然系统耗时变长,怎么去解决?
buffer
方案流程如下:
ID
在buffer1
中,每次获取ID
在buffer1
中获取;buffer1
中的ID
已经使用到了100
,也就是达到区间的10%
;10%
,先判断buffer2
中有没有去获取过,如果没有就立即发起请求获取ID
线程,此线程把获取到的ID
,设置到buffer2
中;buffer1
用完了,会自动切换到buffer2
;buffer2
用到10%
了,也会启动线程再次获取,设置到buffer1
中;buffer
的方案就达到了业务场景用的ID
,都是在jvm
内存中获得的,从此不需要到数据库中获取了,数据库宕机时长长点儿也没太大影响了。buffer
之间自行切换使用,就解决了突发阻塞的问题。还有一些其他的ID
生成方案,比如:
ID
ID
,有的会加上订单第一个商品的ID
;MongoDB
的ID
:通过时间+机器码+pid+inc
共12个字节,4+3+2+3
的方式最终标识成一个24长度的十六进制字符。标签:保存 需求 atomic 生成 集群 分布式ID update 也有 unique
原文地址:https://www.cnblogs.com/vandusty/p/11462585.html