分布式ID

分布式ID介绍

何为ID?

日常开发中,我们需要对系统中的各种数据使用ID唯一表示,比如用户ID对应的是一个人,商品ID对应的是一个商品。

何为分布式ID?

分布式ID是分布式系统下的ID。分布式ID不存在与现实生活中,属于计算机系统一个概念。

假设在多服务器情况下,分库分表(sharding-JDBC)

在分库之后,数据遍布在不同服务器上的数据库,数据库自增主键已经没有办法满足主键唯一了。如何为不同的数据节点生成全局唯一主键呢?

这时候就需要分布式ID了。

分布式ID作为系统不可缺少的一环,很多地方都要用到。

一个最基本的分布式ID需要满足下面要求:

  • 全局唯一:ID的全局唯一性肯定是首先要满足的
  • 高性能:分布式ID生成速度要快,对本地资源消费小
  • 高可用:生成分布式ID的服务要保证可用性无限接近于100%。
  • 方便易用:拿来即用,使用方便

除了这些,一个好的分布式ID还需要保证:

  • 安全:ID不包含敏感信息。
  • 有序递增:如果把ID存放在数据库的话,ID的有序性可以提升数据库的写入速度,可以通过ID来进行排序。
  • 有具体业务含义:S横撑的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

一般情况下,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

UUID包括32个16进制数字。

JDK提供线程声称uuid的方法

UUID.randomUUID();

UUID可以保证唯一性,一般很少用它。

数据库主键尽量越短越好,而UUID的消耗的存储空间比较大。

UUID是无顺序的,InnoDB引擎下,数据库主键的无序性会严重影响数据库的性能。

UUID的优缺点

优点:生成速度比较快

缺点:存储消耗空间比较大、不满全、无序、需要解决重复ID问题

雪花算法(Snowflake)

是推特开源的分布式ID生成算法。

第0位:符号位(标识正负),始终为0。

第1位—41位:一共41位,用来表示时间戳,单位是毫秒,可以支撑2的41次方毫秒。

第42位—52位:一共10位。前五位来说表示机房ID,后为表示机器ID。

第53-64位:一共12位,用来表示序列号。序列号为自增值,代表单台机器每毫秒能够产生最大ID数。

Snonflake算法优缺点:

优点:生成速度比较快、生成的ID有序递增、比较灵活

缺点:需要解决重复ID问题。

你可能感兴趣的:(分布式,分布式,数据库,java)