是什么
分布式id是分布式系统中地一个全局唯一id。
为什么
以前业务量小的时候,进行系统开发一个mysql实例就可以提供服务,再大点的时候进行读写分离也可以应付过来,此时数据库主键自增就可以满足。但随着业务的不断扩展,读写分离扛不住后就需要进行分库分表,但在进行分库分表后就需要一个唯一的ID来标识一条记录。
分布式id的条件
- 全局唯一性:基本要求就是保证id的全局唯一性
- 高性能:高性能低延时,ID生成响应快
- 趋势递增:在MySQL InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
- 信息安全:如果ID是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量。所以在一些应用场景下,会需要ID无规则、不规则。
UUID
UUID(通用唯一编码),它是全球唯一的编码 可以使用,但不推荐
@Test
public void testUUID(){
//利用hutool生成uuid
String uuid = IdUtil.randomUUID();
System.out.println(uuid);
}
UUID的生成非常简单,输出结果为 aad66041-58e2-4fc3-a728-29295086e343(可以去掉中间的 -),但是UUID并不适用于实际的业务开发中。
- 优点
生成简单,本地生成没有网络消耗,具有唯一性
- 缺点
无序的字符串,不具备趋势递增的特性
没有具体的业务含义
不仅长度过长,而且还是字符串,会导致mysql插入删除是数据位置变动频繁,而且字符串作为索引查询慢。
基于数据库自增ID
单数据库模式下使用数据库自增充当分布式ID是一个很好的选择。但是在高并发场景下,就不推荐使用了。
- 优点
实现简单,ID自增,数值类型查询速度快
- 缺点
DB单点存在宕机风险,无法抗住高并发场景
基于数据库集群模式
单点数据库方式扛不住高并发,存在宕机风险,那么就做成多主库模式集群,也就是多个mysql实例都能单独长生自增ID。那么这又会出现一个问题,多个mysql都是从1开始自增,会造成重复的ID。解决方案,设置起始值和自增步长。
假设是两个主库
主库1:
set @@auto_increment_offset = 1; -- 起始值
set @@auto_increment_increment = 2; -- 步长
主库2:
set @@auto_increment_offset = 2; -- 起始值
set @@auto_increment_increment = 2; -- 步长
这样两个库的主键自增分别为:
1 3 5 7 9
2 4 6 8 10
这种方法看似解决了自增和id唯一性的问题,但是随着业务量的增加,两个主库扛不住压力了,就需要进行mysql实例的扩展,此时就麻烦了,如果需要增加第三台主库,那么就需要从新设置三个主库的起始值和步长。
- 优点:
解决了DB单点问题
- 缺点:
不利于后续扩容
基于redis模式
Redis是单线程的并且reids中的incr命令是原子自增的。
redis是第三方的组件,如果本身系统中就没有使用redis,这时使用redis就会增加系统的负担,因为一旦使用就是集群,而且至少得三个哨兵集群。
redis如果使用RBD作为持久化,那在一个快照时间内宕机了,此时还未进行吃就会,恢复后会出现ID重复
使用AOF进行持久化,恢复较慢。(至少丢失1s得数据)
雪花算法
雪花算法时twitter开源得分布式id生成方案。其核心思想就是:使用一个64bit得long型数字作为全局唯一id。
第一部分1个bit:0,二进制的最高位为符号位。0表示整数,1表示负数。
第二部分是41bit的时间戳。单位是毫秒。41bit可以表示的数字多达241-1,也就是可以标识241-1毫秒,约等于69年(从1970年开始)。
第三部分5个bit:表示机房id,最多表示2^5个机房
第四部分为5个bit:表示的是机器id。每个房间里可以有2^5个机器。
第五部分12个bit:表示序号,就是某个机房某台机器上一毫秒内同时生成的id的序号。12bit可以代表的最大正整数是2^12-1,一共4096个数,也就是说一毫秒内可以生成4096个唯一id。
-
优点:
高性能高可用,生成时不依赖于数据库,完全在内存中生成。
容量大:每秒能生成百万的自增id
id自增:时间时自增的,所以生成的id也是自增的。 -
缺点:
依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能造成重复的id。