分库分表订单全局ID
概述
分库分表后涉及到的另一个问题就是主键如何保证唯一且自增。以前单库单表的时候只需要利用数据库特性进行自增即可,现在因为是各自独立的库表,数据库之间的主键自增无法进行交互,比如数据库1的订单明细表主键自增到了1001,数据库2的订单明细表主键现在是1000,如果现在往数据库2的订单明细表中插入一条数据,这个时候获取到的主键ID会是1001,这样就会造成业务上的主键冲突。
全局ID
为了解决订单明细表主键的重复问题。靠数据库的主键自增是无法做到了。如何解决这个问题呢?可以给项目中引入一个全局唯一的ID服务,这个服务就是用来生成全局唯一ID的,每次生成的ID都不一样,可以保证主键的唯一性。
全局ID算法
全局ID需要保证如下的特性:
- 全局唯一:必须保证ID是全局性唯一的,基本要求
- 高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈
- 高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性
- 好接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单
- 趋势递增:最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求
常用的全局ID算法如下:
- 雪花算法Snowflake
- 百度uid-generator
- 美团Leaf
- 滴滴Tinyid
雪花算法
雪花算法的结构:
主要分为 4 个部分:
- 是 1 个 bit:0,这个是无意义的。
- 是 41 个 bit:表示的是时间戳。
- 是 10 个 bit:表示的是机房 id,0000000000,因为我传进去的就是0。
- 是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 0000 0000。
1 bit,是无意义的:
因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
41 bit:表示的是时间戳,单位是毫秒。
41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成>年就是表示 69 年的时间。
10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。
但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器),这里可以随意拆分,比如拿出4位标识业务号,其他6位作为机器号。可以随意组合。
12 bit:这个是用来记录同一个毫秒内产生的不同 id。
12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。也就是同一毫秒内同一台机器所生成的最大ID数量为4096
简单来说,你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。这个 SnowFlake 算法系统首先肯定是知道自己所在的机器号,(这里姑且讲10bit全部作为工作机器ID)接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 id,64 个 bit 中的第一个 bit 是无意义的。接着用当前时间戳(单位到毫秒)占用41 个 bit,然后接着 10 个 bit 设置机器 id。最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 id 的请求累加一个序号,作为最后的 12 个 bit。
- 时钟回拨
因为雪花算法中包含时间戳,因此依赖系统的时间,如果系统的时间由于某一些原因回到了过去的某个时间,比如现在的系统时间是2022年8月7日09时01分10秒,但是下次获获取到的时间是2022年8月7日09时00分10秒,那么就可能导致生成的全局ID与过去的某一个ID重复,这就是时钟回拨问题。
为了解决时钟回拨问题可以把之前的系统获取到哦啊的时间戳缓存起来,每次获取时间戳和上次的进行比较,如果本次获取的时间小于上一次的时间,就证明时钟回拨了,就可以取上次时间戳+1来解决。
总结
其实对于分布式ID的生成策略。无论是我们上述提到的哪一种。无非需要具有以下两种特点。 自增的、不重复的 ,而对于不重复且是自增的,那么很容易想到的是时间,而雪花算法就是基于时间戳。但是毫秒级的并发下如果直接拿来用,显然是不合理的。那么就要在这个时间戳上面做一些文章。至于怎么能让这个东西保持唯一且自增。就要打开自己的脑洞了。可以看到雪花算法中是基于 synchronized 锁进行实现的。