日常开发中,我们需要对系统中的各种数据使用ID唯一表示,比如用户ID对应的是一个人,商品ID对应的是一个商品。
分布式ID是分布式系统下的ID。分布式ID不存在与现实生活中,属于计算机系统一个概念。
假设在多服务器情况下,分库分表(sharding-JDBC)
在分库之后,数据遍布在不同服务器上的数据库,数据库自增主键已经没有办法满足主键唯一了。如何为不同的数据节点生成全局唯一主键呢?
这时候就需要分布式ID了。
分布式ID作为系统不可缺少的一环,很多地方都要用到。
一个最基本的分布式ID需要满足下面要求:
除了这些,一个好的分布式ID还需要保证:
1.创建一个数据库表
CREATE TABLE `sequence_id` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`stub` char(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
stub字段为了占位,便于插入或者修改数据,并且建立唯一索引,确保唯一性。
2.通过replace into插入数据。
在这里没有用insert into而用replace into来插入数据,具体步骤是这样的:
(1)第一步:尝试把数据插入表中
(2)第二步:如果主键或者唯一索引字段出现重复数据错误而插入失败时候,先从表中删除含有重复关键字值的冲突航,然后再次尝试把数据插入到表中。
这种方式的优缺点也比较明显:
优点:实现起来比较简单、ID有序递增、存储消耗空间小。
缺点:支持并发量不大、存在数据库单点问题、ID没有具体业务含义、每次获取ID都要访问一次数据库
数据库主键自增这种模式,每次获取ID都要访问一次数据库,当ID需要比较大的时候,肯定是不行的。
如果我们批量获取,然后存到内存里面,需要用得到时候,从内存拿出来就行,这就是数据库的号段模式来生成分布式ID
数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的就是基于这种方式来做的。不过,TinyId 使用了双号段缓存、增加多 db 支持等方式来进一步优化。
以MySQL举例
1.创建一个数据库表。
CREATE TABLE `sequence_id_generator` (
`id` int(10) NOT NULL,
`current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
`step` int(10) NOT NULL COMMENT '号段的长度',
`version` int(20) NOT NULL COMMENT '版本号',
`biz_type` int(20) NOT NULL COMMENT '业务类型',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
current_max_id字段和step字段主要用于获取批量ID,获取的批量id为:
current_max_id~current_max_id+step。
version主要用于解决并发问题(乐观锁),biz_type主要用于表示业务类型。
2.插入一行数据
INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES
(1, 0, 100, 0, 101);
3.通过SELECT获取指定业务下的批量唯一ID
SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101
4.不够用的话,更新之后重启SELECT
UPDATE sequence_id_generator SET current_max_id = 0+100, version=version+1 WHERE version = 0 AND `biz_type` = 101
SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101
相比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小
为了避免单点问题,可以使用主从模式提高可用性。
数据库号段模式的优缺点:
优点:ID有序递增、存储消耗空间小
缺点:存在数据库单点问题、ID没有具体业务含义、安全问题
一般情况下,NoSQL方案使用redis多一些。通过redis的incr命令即可实现对id原子顺序递增。
127.0.0.1:6379> set sequence_id_biz_type 1
OK
127.0.0.1:6379> incr sequence_id_biz_type
(integer) 2
127.0.0.1:6379> get sequence_id_biz_type
"2"
为了提高可用性和并发,我们可以使用Redis Cluster。Redis Cluster是官方提供的redis集群解决方案。
除了Redis Cluster之外,可以用Redis集群方案Codis(大规模集群)
除了高可用和并发之外,我们知道redis基于内存,我们需要持久化数据,避免机器重启后数据丢失。redis有两种持久化方式:aof和rdb。这里具体就不介绍了,后期专门出一期redis的文章。
Redis方案的优缺点
优点:性能好,生成有序递增的ID
缺点:和数据库主键自增方案缺点类似
除此之外,MongoDB也可以用作分布式ID解决方案。
mongoDb需要十二个字节存储
0-3:时间戳
3-6:机器ID
7-8:机器进程ID
9-11:自增值
mongoDB方案优缺点:
优点:性能不错并且声称的ID是有序递增的
缺点:需要解决重复ID问题
UUID包括32个16进制数字。
JDK提供线程声称uuid的方法
UUID.randomUUID();
UUID可以保证唯一性,一般很少用它。
数据库主键尽量越短越好,而UUID的消耗的存储空间比较大。
UUID是无顺序的,InnoDB引擎下,数据库主键的无序性会严重影响数据库的性能。
UUID的优缺点
优点:生成速度比较快
缺点:存储消耗空间比较大、不满全、无序、需要解决重复ID问题
是推特开源的分布式ID生成算法。
第0位:符号位(标识正负),始终为0。
第1位—41位:一共41位,用来表示时间戳,单位是毫秒,可以支撑2的41次方毫秒。
第42位—52位:一共10位。前五位来说表示机房ID,后为表示机器ID。
第53-64位:一共12位,用来表示序列号。序列号为自增值,代表单台机器每毫秒能够产生最大ID数。
Snonflake算法优缺点:
优点:生成速度比较快、生成的ID有序递增、比较灵活
缺点:需要解决重复ID问题。