在分布式环境中,由于多负载,高并发等原因,很多业务都需要分布式ID生成器来生成他们的唯一ID,其实方案有很多,今天就结合我们自己使用和了解过的几种方案来记录一下。希望多你也有帮助。
UUID
UUID是我们很常见的一个ID方案,理论上唯一,生成方式简单。
但ID本身无意义所以不适合做ID需要表示含义的内容,如需要像客户展示的订单号;而且总长36位过长,去除"-"后也有32位,存储到DB占用存储空间大。而且作为聚集索引的话,每次去比较转换添加新的索引时因为b+tree要排序,会有严重的性能问题。
UUID适合一些ID无意义的场景。如我们在架构中应用到分布式链路追踪的ID上。
mysql 自增
这种方案其实就是利用mysql的自增主键,因为mysql主键是聚集索引,物理上连续,并且自增主键存储占用少,效率高;但mysql存在单点问题,不利于数据迁移。而且db会有压力和瓶颈。我们在一些不太容易变动的如管理员表,菜单表中使用自增主键。以达到最高的效率。并且没有分库分表,迁移等问题。
mysql自增+step
mysql自增方案有单点问题,所以在mysql集群下使用自增主键+步长方案,比如3台mysql,步长设置3,那么第一个mysql中的主键就是1 4 7,第二台是2 5 8,第三台是3 6 9。
但step一旦设定好无法扩容。集群无法扩容。我认为该方案实用性一般。目前没有实践过。
redis
分布式系统中采用redis做缓存很常见。我们也不例外。redis是基于内存的一种nosql数据库,数据操作是单线程,利用原子性的incr可以获取自定义的自增ID,比如取用当前时间如2019101010222,再从redis操作incr获取自增序列拼接。好处就是灵活自定义,ID有含义,有趋势递增,缺点是占用网络IO。效率一般。我们目前使用该方案做销售、出库、采购等内部流转订单编号。
snowflake
这个就很知名了。64位2进制数,第一位不用➕41位时间戳➕10位机器码(或5位机房码+5位机器码,可自由定制)➕12位序列(毫秒内计数),理论上毫秒内可生成1024*4096个id,并且趋势递增。
但缺点是依赖服务器时钟,存在时间回拨问题,如果自己实现该算法可记录上一次时间戳,进行比较,短回拨可等待,长回拨只能抛出异常。我们使用的mybatis plus内置的IdWorker来做对外订单号。目前倒是还没出现什么问题。
mysql的buffer方案
做一个id表,根据不同业务,设置每个业务的起时值,当前最大值,业务服务每次请求获取一个ID区间放入自身jvm中。然后更新起始值和最大值,当有ID需要使用时,业务服务在jvm内使用线程安全方式去获取ID值。这样就完美解决了每次获取id到要去查db的性能问题以及步长导致的无法扩容问题,并且就算业务系统挂掉,重新获取ID区间,也不会出现重复问题,当然也可做单独的ID生成服务。
但这个方案问题是如果业务服务负载众多时,会出现多个服务同时用完当前ID区间去请求新的区间,这也可以通过分布式锁,或者db锁保证区间的唯一,但是还是会出现请求服务一会快一会慢的情况。
mysql的双buffer方案
为了解决buffer中忽快忽慢问题,设计使用双buffer,即在jvm内配置两个存储id区间的buffer ,当第一次请求获取的区间放到buffer1中,当使用达到10%时,另起线程请求新的ID区间,放入buffer2中.当buffer1中ID用尽,自动切换到buffer2,同理,buffer2用到10%时请求新的放入buffer1。但这个方案仍无法解决大面积宕机或者迭代升级后,服务重启导致的多实例同时去获取ID区间的并发问题。
记录了这么多方案,其实每个方案都有自己的最佳场景。选择适合自己架构的才是最好的。如果并发没有一线互联网公司这么高,其实没有必要上双buffer,只不过是给自己徒增麻烦。如果也根本不迁移,不会涉及到分库分表,其实自增ID更香一些。所以方案可以多了解,但是最重要选择一个适合自己业务场景的。