问题的产生
在传统的单体架构中,很少进行数据库/表的拆分,但是在现如今的分布式架构中,随着业务的不断发展,单表的数据量过大,会大大的影响数据的查询效率,这个时候会考虑进行分表分库。但这时会产生一个问题: 在未进行分库分表时,在一张表中,Mysql可以保证我们的字段全局唯一,在分库分表后,我们要继续保证某些字段(比如主键ID)在整个全局是唯一的,这时候该如何实现呢?
解决方案
UUID
UUID是一种通用唯一标识符,其有不同的版本实现,其中java.util包下对UUID进行了实现。 UUID重复的概率极低,世界上每一个人拥有6亿个UUID,重读一个UUID的概率也只有50%,因此在分布式系统中,要保证ID全局唯一,使用UUID是可以做到的。
- 缺点:虽然UUID虽然能够保证全局唯一,却存在一些缺点,UUID的长度太长,每次都生成36位的长度,对于数据库来说并不友好,UUID属于无序ID,在数据库中的存储位置频繁变动也会影响数据库的性能。
基于数据库自增ID
在分库分表后,我们虽然不能保证每个分表生成的ID在全局唯一,但是我们可以额外增加一台数据库(ID库),专门用来生成ID,这个时候这台数据库就能够保证生成的ID唯一,我们的业务数据库在每次需要一个ID时,就向我们的ID库插入一条数据,这是我们就能拿到ID库新生成的ID,保证了ID的全局唯一。
- 缺点:这种方式虽然保证了ID全局唯一,但是如果ID库因为某些因素突然宕掉,整个分布式系统将面临风险
基于集群的数据库自增ID
上一种方式的缺点在于不能保证ID库的高可用,那么我们可以从这个地方入手,将ID库搭建成集群,来保证ID库的高可用状态,这个时候,怎样在集群的情况下保证业务数据库拿到的ID仍然是唯一的呢?我们可以通过给每个ID库设置ID自增的步长(step),例如现在打算搭建3个ID库的集群,一号ID库从1开始自增,每次自增3,二号ID库从2开始,每次自增3,三号ID库从3开始,每次自增3,这样循环下去,每个ID库拿到的ID都是不同的,有效的保证了ID唯一且ID库处于高可用状态。
- 缺点:这种方式很好的保证了ID唯一,但是有存在不利于拓展的问题。
基于数据库的号段模式
号段模式的核心思在于,每次从ID库批量获取ID,例如,在ID库中每次获取1000个ID(设置一个字段,可以自定义每次获取的ID数量),缓存到本地,另一个服务再次请求ID库的时候就会从1001开始取到2000。这种方式需要考虑并发问题,防止两个服务同时请求ID库拿到相同的ID,我们需要在ID库中增加version字段,每次请求返回,version加1,避免两个请求拿到相同的ID。
- 缺点:这种方式的好处在于不会强依赖于ID库,即使ID库宕掉,仍可以维持一段时间。
基于Redis的模式
利用Redis的incr可以实现获取ID唯一,因为Redis是单线程数据库,即使在高并发的情况下,仍然可以保证ID唯一。
- 缺点:Redis在数据持久化时可能丢失数据(RDB),在Redis重启时可能从已经获取过的ID开始自增,导致ID重复。如果使用AOF方式持久化,虽然不会丢失数据,但是重启时间过长。
雪花算法(snowflake)
利用特殊生成策略保证生成的ID唯一,雪花算法是谷歌公司的分布式ID解决方案。由64位的二进制数组成,第一位为符号位,不做处理,第2到42位为时间戳位,第43到52为机器标识位(可根据实际情况定制),代表哪个机房的哪一台机器,最后12位为序列号位,相同机房相同机器相同时间戳的ID在序列号位基础上递增。此种方式不依赖于网络,并且生成的ID有序自增,利于数据库的索引维护。
总结
以上就是分布式ID的常见解决方案了,如果你有疑问或者更好的建议,欢迎评论区留言讨论!