前言
看到标题是不是很多初学者都会有疑问?不是mysql自增id吗,为什么还要讲分布式唯一ID。带着这个疑问去想一个问题,如果我的程序是分库分表的,怎么保证两个不同库不同表的主键id不同呢?是不是mysql自增id就不能满足了。比如说订单表由于数据量比较大,分了6个库,总不能这6个库的订单表id重复吧。但是办法总比困难多,现在业界一共有4种方案来解决这个问题。
一、UUID
uuid:uuid全称是通用唯一识别码(Universally Unique Identifier)。底层是通过mac地址+时间+随机数进行生成的128位的二进制,转换为16进制字符串后,长度为32位。
uuid的生成非常简单, 如果用java代码生成,那只需要一行代码
public static void main(String[] args) {
String uuid = UUID.randomUUID().toString().replaceAll("-","");
System.out.println(uuid); }
uuid永远不会重复。是不是和id的唯一性正好吻合。他有很多优点和缺点:
优点:简单,不需要引用任何其他的组件就可以生成,而且生成速度也还可以。
缺点:生成的id是无序的,而且也不是递增的。这个缺点很致命。因为我们很多时候我的id需要反应业务属性,比如说我们在做排序的时候,需要根据时间排序,我们可以直接order by id,这样既可以命中索引,也可以满足我们的需求,但是采取uuid以后,就不能采用这种方案了,我们只能order by create_time了.同时在底层插入索引时,还会引起索引节点的分裂,影响插入速度。 所以用UUID生成的ID,对数据库来说很不友好。
但是并不是UUID没有任何用处,存在即合理,我们在生成唯一字符串的时候还是可以使用UUID,很多链路追踪框架的链路id就是用的UUID。
二、数据库自增
数据库有一个很重要的功能就是序列,我们可以使用这个序列来做唯一id生成。简单的说就是使用数据库自带的id之所以不能用作分布式唯一id是因为每个库的每个表的自增id是相同的,比如说a库可以从10增到11,b库也可以从10增到11,那我们都使用一个序列不就行了,所有的id都从这一个序列产生不就可以了吗,这个就是数据库自增。
数据库自增的实现也很简单:
INSERT INTO order(id,column1,,column2) VALUES (SELECT LAST_INSERT_ID(),'value1', 'value2') ;
数据库自增方式也有很多优点和缺点:
优点:生成的id是递增的。
缺点:效率低下,高并发的条件下基本上pass这种方案。因为每次生成一个id都需要查询一次数据库,本来高并发条件下数据库的压力就非常大,这种方式反而又增加了数据库的负担。
三、号段模式
对比上面的数据库自增模式的瓶颈是每次获取id都需要访问一次数据库,数据库号段模式完美的解决这个问题。现在我一次性的取100个id,存到我们的内存jvm内存当中,等用完了我再取,是不是数据库的访问次数就少多了。
上图中server01中的每次需要insert的时候,都从0-1000递增取id,server02中的每次需要insert的时候,都从1000-2000递增取id。号段模式每次操作数据库是申请一个号段的范围。比如操作一次数据库,申请1000到2000是这个应用的业务申请的号段;然后只能这个应用实例的业务能用;应用申请了号段后放到内存中,每次业务需要的时候,从内存里累加1返回,在java里也有现成的工具,比如AtomicLong包装下(省事还能解决并发问题);如果发现快不够了,还能异步提前从数据库中申请,放入内存中,这样就避免了业务需要唯一ID的时候,在去数据库申请,加快业务获取ID的速度。
(注意:在使用此方案的时候,一定要注意线程安全,一定要注意线程安全,一定要注意线程安全!)
优点:既能满足唯一性,也能满足递增性,而且性能很可观,美团开源的leaf使用此种模式,速度能到 5W/秒,滴滴开源的Tinyid也是用的此模式。
缺点:相比其他方案,这种方案实现起来比较繁琐。
四、雪花算法(Snowflake ID)
雪花算法出自推特公司,它和UUID比较类似。
Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特),总共64比特。
看上面的组成部分:时间戳。也就是说雪花算法是和时间戳有关系的,而时间戳肯定是递增的,也就是说雪花算法也是递增的,但是它的这种递增是基于时间的递增,不像号段模式生成的id根据业务递增。百度也基于雪花算法思想,开发了自己开源的 UidGenerator 分布式ID生成器。
现在mybaits-plus框架也集成了雪花算法。
@TableId(type = IdType.ASSIGN_ID)
private Long id;
优点:递增,开箱即用,不需要引入其他组件
缺点:因为雪花算法是基于时间戳生成,也就意味着我们在使用雪花算法的时候,要保证服务器的时间必须一致,如果两台服务器的时间不一致,有可能会生成重复的id(时钟回拨问题)
二、具体使用
1.雪花算法
现在有很多现成的三方工具类都已经集成了雪花算法。这里使用一个非常经典的小而全的工具包:hutool。
1.引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
2,生成雪花算法id
long snowflakeNextId = IdUtil.getSnowflakeNextId();
2.号段模式(美团leaf)
1,引入依赖
<dependency>
<groupId>com.tencent.devops.leaf</groupId>
<artifactId>leaf-core</artifactId>
<version>1.0.2-RELEASE</version>
</dependency>
2,使用
IDGen idGen = new SegmentIDGenImpl();
long order = idGen.get("order").getId();
可以看一下IDGen 的源码,这个接口提供了三种id生成策略,SegmentIDGenImpl就是号段模式。
传送门:
美团leaf源码
百度UidGenerator源码
滴滴Tinyid源码
总结
由于篇幅本文代码部分相对较少,感兴趣的猿们可以评论或者后台私我,给你们更详细的集成代码demo。
点赞+关注的人明天肯定能收到好消息!!!