likes
comments
collection
share

Mysql分库分表相关问题之分布式全局唯一id

作者站长头像
站长
· 阅读数 47

前言

上一篇文章《Mysql分库分表》中介绍了为啥要分库分表及对应的策略,但是也会引发一系列的问题,本文就来介绍“分布式全局唯一id问题”怎么解决。

分布式全局唯一id问题

自增整数能否继续使用?

一般在单库上都会使用自增整数作为主键,因为占用空间小、检索速度快、io消耗低、避免页分裂等优点。那么我们再分布式数据库中是否还能继续使用呢?

继续使用unsigned bigint类型理论上是可以的,上限足够大。

  • 设置步长:mysql本身是支持设置起始值auto_increment_offset和步长auto_increment_increment的。但是这种方式不利于水平分表的继续分,扩容需要重新设置步长。
  • 进行分区:比如指定一个表只能存放3kw条数据,假设有两个个水平分表t1,t2。那么t1自增id的范围为[0,29999999],t2的自增id范围为[30000000,59999999],以此类推。

如果使用分区,既可用又可以不用动老数据进行扩容,t1、t2可以使用负载均衡自动插入到对应的分表中也能解决集中写的性能瓶颈,查询的时候也可以根据范围找到对应的分表也能解决集中读的性能瓶颈问题。如果一个库已经达到上限就移出写入的负载均衡。

结论

可以,但是对表进行分区需要知道对应表id取值范围、需要控制不能超过、达到了上限需要移出写的负载均衡,实现起来不算复杂。

SnowFlake-雪花算法

Mysql分库分表相关问题之分布式全局唯一id

一共64位,最终可以使用bigint存储

  • 最高位固定为0,使用正整数
  • 41位表示毫秒级的时间戳
  • 10位用来表示机器id,分别使用5位表示dataCenterId和workerId。
  • 12为用力啊表示序列号,从0开始。

结论

可以看的出来,在并发不是很高的情况下产生的数字也是递增的,能和自增id达到差不多的效果,使用起来比较简单,但是为了分表中的数据比较均匀,需要对写进行负载均衡,同时需要监控单个分表的数据量是否到上限进而需要再次水平分。

这样看来和自增整数id实现复杂度和最终效果都差不多。

java引入第三方库生成

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.20</version>
</dependency>

注意需要分配dataCenterId和workerId。

System.out.println(new SnowflakeGenerator(0,0).next());

定制具体的生成全局唯一id规则

上面的两种方案其实可以发现一个明显缺点,就是如果进行条件筛选需要对所有分表进行扫描,虽然你可以使用多线程并线去查最终合并返回数据,但是盲目的去各个分表中查询无疑会带来性能(多线程查询消耗、各个分库查询压力)的瓶颈,那么针对这个可以根据业务定制具体的生成全局唯一id规则进行优化(比如美团的订单分库分表)。

Mysql分库分表相关问题之分布式全局唯一id

美团的id的生成规则是:> 时间戳+用户标识码+随机数,分库的话就是根据用户标识码去做运算最后取模。

优点

因为大部分场景都是根据业务主体标识去查询(比如上面的根据用户标识),所以这个优化能满足业务中的很多场景。

缺点

但是满足不了所有场景,比如根据shopid进行查询订单就还需要对所有分表进行扫描。另外生成的id长度略长会对io、检索等产生不利影响,至于页分裂还好,因为有时间戳,并发不大的时候不会产生。还有一个问题就是数据分布极端情况会特别不均匀。

总结

从上面的分析可以看的出来,自增整数和SnowFlake的效果类似,都具有占用空间小、检索速度快、io消耗低、避免页分裂的优点,但是因为没有结合业务进行优化所以笔者认为大部分场景应该是选择第三种方案。