数据库|分布式id生成策略,我和面试官扯了一个半小时( 二 )

我:我这里假设有三个数据库 , 为每一个数据库设置初始值 , 设置初始值可以通过下面的sql进行设置:
set @@auto_increment_offset = 1;     // 设置初始值set @@auto_increment_increment = 2;  // 设置步长复制代码

我:三个数据的初始值分别设置为1、2、3 , 一般步长设置为数据库的数据 , 这里数据库数量为3 , 所以步长也设置为3 。
面试官:若是面对再次扩容的情况呢?
我:恩额 , 扩容的情况是这种方法的一个缺点 , 上面我说的步长一般设置为数据库的数量 , 这是在确保后期不会扩容的情况下 , 若是确定后期会有扩容情况 , 在前期设计的的时候可以将步长设置长一点 , \t「 预留一些初始值给后续扩容使用\t」 。
我:总之 , 这种方案还是优缺点的 , 但是也有自己的优点 , 缺点就是:\t「 后期可能会面对无ID初始值可分的窘境 , 数据库总归是数据库 , 抗高并发也是有限的\t」  。
我:它的优点就是算是解决了\t「 DB单点的问题\t」 。
面试官:恩额 。
批量申请自增ID我:\t「 批量申请自增ID\t」的解决方案可以解决无ID可分的问题 , 它的原理就是一次性给对应的数据库上分配一批的id值进行消费 , 使用完了 , 再回来申请 。
这次我很自觉的从裤兜里拿出笔和纸 , 画出了下面的这张图 , 历史总是那么惊人的相似 。


我:在设计的初始阶段可以设计一个有初始值字段 , 并有步长字段的表 , 当每次要申请批量ID的时候 , 就可以去该表中申请 , 每次申请后\t「 初始值=上一次的初始值+步长\t」 。
我:这样就能保持初始值是每一个申请的ID的最大值 , 避免了ID的重复 , 并且每次都会有ID使用 , 一次就会生成一批的id来使用 , 这样访问数据库的次数大大减少 。
我:但是这一种方案依旧有自己的缺点 , 依然不能抗真正意义上的高并发 。
UUID生成我:第四种方式是使用\t「 UUID生成\t」的方式生成分布式ID , UUID的核心思想是使用\t「 机器的网卡、当地时间、一个随机数」来生成UUID 。
我:使用UUID的方式只需要调用\tUUID.randomUUID().toString() 就可以生成 , 这种方式方便简单 , 本地生成 , 不会消耗网络 。
我:当时简单的东西 , 出现的问题就会越多 , 不利于存储 , 16字节128位 , 通常是以36位长度的字符串表示 , 很多的场景都不适合 。
我:并且UUID生成的无序的字符串 , 查询效率低下 , 没有实际的业务含义 , 不具备自增特性 , 所以都不会使用UUID作为分布式ID来使用 。
面试官:恩额 , 那你知道生成UUID的方式有几种吗?不知道没关系 , 这个只是作为一个扩展 。
我:这个我只知道可以通过\t「 当前的时间戳及机器mac地址」来生成 , 可以确保生成的UUID全球唯一 , 其它的没有了解过 。
面试官:嗯嗯 , 没关系的 。
Redis的方式我:为了解决上面纯关系型数据库生成分布式ID无法抗高并发的问题 , 可以使用Redis的方式来生成分布式ID 。
我:Redis本身有\tincr 和\tincreby 这样自增的命令 , 保证原子性 , 生成的ID也是有序的 。
我:Redis基于内存操作 , 性能高效 , 不依赖于数据库 , 数据天然有序 , 利于分页和排序 。
我:但是这个方案也会有自己的缺点 , 因为增加了中间件 , 需要自己编码实现工作量增大 , 增加复杂度 。
我:使用Redis的方式还要考虑持久化 , Redis的持久化有两种\t「 RDB和AOF\t」 , \t「 RDB是以快照的形式进行持久化 , 会丢失上一次快照至此时间的数据\t」  。
我:\t「 AOF可以设置一秒持久化一次 , 丢失的数据是秒内的」 , 也会存在可能上一次自增后的秒内的ID没有持久化的问题 。


推荐阅读