vlambda博客
学习文章列表

分布式环境下获取全局唯一ID

ID生成规则部分硬性要求


  • 全局唯一


  • 趋势递增


在MySQL的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引,在主键的选择上面我们应该尽量使用有序的主键保证写入性能


  • 单调递增


保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求


  • 信息安全


如果ID是连续,恶意用户的爬取工作就非常容易做了,直接按照顺序下载指定URL即可,如果是订单号就危险了,竞争对手可以直接知道我们一天的单量,所以在一些应用场景下,需要ID无规则不规则,让竞争对手不好猜。


  • 含时间戳


一样能够快速在开发中了解这个分布式ID什么时候生成的


ID号生成系统的可用性要求


  • 高可用


发布一个获取分布式ID请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID


  • 低延迟


发一个获取分布式ID的请求,服务器就要快,极速


  • 高QPS


例如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶得住且一下子成功创建10万个分布式ID


解决方案


  • UUID


UUID.randomUUID() , 标准型包含32个16进制数字,性能高,本地生成,没有网络消耗。


问题:

1、无序,无法保证趋势递增,入库性能差。

2、不好加索引,查询的效率比较低。


  • 数据库自增主键


REPLACE into t_test(stub) values('b');select LAST_INSERT_ID();


不同机器有不同的起始数字和步长。


优点:

1、简单,代码方便,性能可以接受。

2、数字ID天然排序,方便业务使用。


缺点:

1、分布式不友好,有单点故障的风险。

2、在性能达不到要求的情况下,比较难于扩展。

3、分表分库的时候会有麻烦。


  • Redis生成


可以使用Redis原子操作INCR和INCRBY来实现。


比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。


优点:

1、不依赖于数据库,灵活方便,且性能优于数据库。

2、数字ID天然排序,对分页或者需要排序的结果很有帮助。


缺点:

1、如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。

2、需要编码和配置的工作量比较大。


  • snowflake算法


结果是一个long型的ID,其核心思想是:高位随机+毫秒数+机器码(数据中心+机器id)+10位的流水号码。



snowflake生产的ID是一个18位的long型数字,二进制结构表示如下(每部分用-分开):0 - 00000000 00000000 00000000 00000000 00000000 0 - 00000 - 00000 - 00000000 0000第一位未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年,从1970-01-01 08:00:00),然后是5位datacenterId(最大支持2^5=32个,二进制表示从00000-11111,也即是十进制0-31),和5位workerId(最大支持2^5=32个,原理同datacenterId),所以datacenterId*workerId最多支持部署1024个节点,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生2^12=4096个ID序号).所有位数加起来共64位,恰好是一个Long型(转换为字符串长度为18).单台机器实例,通过时间戳保证前41位是唯一的,分布式系统多台机器实例下,通过对每个机器实例分配不同的datacenterIdworkerId避免中间的10位碰撞。最后12位每毫秒从0递增生产ID,再提一次:每毫秒最多生成4096个ID,每秒可达4096000个


优点:

1. 不依赖于数据库,灵活性能优于数据库。

2. ID按照时间在单机上是递增的。


缺点:

在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。


SpringBoot整合雪花算法

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