分布式ID实现方法

什么是分布式 ID?

分布式 ID 是分布式系统下的 ID。分布式 ID 不存在与现实生活中,属于计算机系统中的一个概念。我简单举一个分库分表的例子。我司的一个项目,使用的是单机 MySQL 。但是,没想到的是,项目上线一个月之后,随着使用人数越来越多,整个系统的数据量将越来越大。单机 MySQL 已经没办法支撑了,需要进行分库分表(推荐 Sharding-JDBC)。在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?
这个时候就需要生成分布式 ID

在这里插入图片描述

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

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

除了这些之外,一个比较好的分布式 ID 还应保证:

  • 安全 :ID 中不包含敏感信息。
  • 有序递增 :如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候 ,我们还很有可能会直接通过 ID 来进行排序。
  • 有具体的业务含义 :生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
  • 独立部署 :也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID 的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID 的场景比较多的话,独立部署的发号器服务还是很有必要的。

常见解决方案

数据库

数据库主键自增

这种方式就比较简单直白了,就是通过关系型数据库的自增主键产生来唯一的 ID。

以 MySQL 举例,我们通过下面的方式即可。

  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 字段无意义,只是为了占位,便于我们插入或者修改数据。并且,给 stub 字段创建了唯一索引,保证其唯一性。

  1. 通过 replace into 来插入数据.
BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;

插入数据这里,我们没有使用 insert into 而是使用 replace into 来插入数据。

replace into原理:replace intoinsert 功能类似,不同点在于:replace into 首先尝试插入数据到表中,

  1. 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据。
  2. 否则没有此行数据的话,直接插入新数据。

优缺点:

  • 优点:实现起来比较简单、ID 有序递增、存储消耗空间小
  • 缺点:支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)

数据库号段模式

数据库主键自增这种模式,每次获取 ID 都要访问一次数据库,ID 需求比较大的时候,肯定是不行的。

如果我们可以批量获取,然后存在在内存里面,需要用到的时候,直接从内存里面拿就舒服了!这也就是我们说的 基于数据库的号段模式来生成分布式 ID。

数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的Tinyid 就是基于这种方式来做的。不过,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 主要用于表示业务类型。

相比于数据库主键自增的方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小

优缺点:

  • 优点 :ID 有序递增、存储消耗空间小
  • 缺点: 存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、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 集群解决方案(3.0+版本)。

除了 Redis Cluster 之外,你也可以使用开源的 Redis 集群方案Codis (大规模集群比如上百个节点的时候比较推荐)。

除了高可用和并发之外,我们知道 Redis 基于内存,我们需要持久化数据,避免重启机器或者机器故障后数据丢失。Redis 支持两种不同的持久化方式:快照(snapshotting,RDB)、只追加文件(append-only file, AOF)。

优缺点

  • 优点: 性能不错并且生成的 ID 是有序递增的
  • 缺点: 和数据库主键自增方案的缺点类似

算法

UUID

UUID 是 Universally Unique Identifier(通用唯一标识符) 的缩写。UUID 包含 32 个 16 进制数字(8-4-4-4-12)

JDK 就提供了现成的生成 UUID 的方法,一行代码就行了。

//输出示例:cb4a9ede-fa5e-4585-b9bb-d60bce986eaa
UUID.randomUUID()

我们这里重点关注一下这个 Version(版本),不同的版本对应的 UUID 的生成规则是不同的。

5 种不同的 Version(版本)值分别对应的含义

  1. 版本 1 : UUID 是根据时间和节点 ID(通常是 MAC 地址)生成;
  2. 版本 2 : UUID 是根据标识符(通常是组或用户 ID)、时间和节点 ID 生成;
  3. 版本 3、版本 5 : 版本 5 - 确定性 UUID 通过散列(hashing)名字空间(namespace)标识符和名称生成;
  4. 版本 4 : UUID 使用随机性或伪随机性生成。

下面是 Version 1 版本下生成的 UUID 的示例:
在这里插入图片描述

JDK 中通过 UUIDrandomUUID() 方法生成的 UUID 的版本默认为 4。

UUID uuid = UUID.randomUUID();
int version = uuid.version();// 4

UUID 可以保证唯一性,因为其生成规则包括 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,计算机基于这些规则生成的 UUID 是肯定不会重复的。

虽然,UUID 可以做到全局唯一性,但是,我们一般很少会使用它。

比如使用 UUID 作为 MySQL 数据库主键的时候就非常不合适:

  • 数据库主键要尽量越短越好,而 UUID 的消耗的存储空间比较大(32 个字符串,128 位)
  • UUID 是无顺序的,InnoDB 引擎下,数据库主键的无序性会严重影响数据库性能。

优缺点:

  • 优点 :生成速度比较快、简单易用
  • 缺点 : 存储消耗空间大(32 个字符串,128 位) 、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)

Snowflake(雪花算法)

如果你想要使用 Snowflake 算法的话,一般不需要你自己再造轮子。有很多基于 Snowflake 算法的开源实现比如美团 的 Leaf、百度的 UidGenerator,并且这些开源实现对原有的 Snowflake 算法进行了优化。

优缺点 :

  • 优点 :生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
  • 缺点 :需要解决重复 ID 问题(依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID)

开源框架

UidGenerator(百度)

UidGenerator 是百度开源的一款基于 Snowflake(雪花算法)的唯一 ID 生成器。

Leaf(美团)

Leaf 是美团开源的一个分布式 ID 解决方案 。这个项目的名字 Leaf(树叶) 起源于德国哲学家、数学家莱布尼茨的一句话: “There are no two identical leaves in the world”(世界上没有两片相同的树叶)

Leaf 提供了 号段模式Snowflake(雪花算法) 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper 。

Tinyid(滴滴)

Tinyid 是滴滴开源的一款基于数据库号段模式的唯一 ID 生成器。

相比于基于数据库号段模式的简单架构方案,Tinyid 方案主要做了下面这些优化:

  • 双号段缓存 :为了避免在获取新号段的情况下,程序获取唯一 ID 的速度比较慢。 Tinyid 中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
  • 增加多 db 支持 :支持多个 DB,并且,每个 DB 都能生成唯一 ID,提高了可用性。
  • 增加 tinyid-client :纯本地操作,无 HTTP 请求消耗,性能和可用性都有很大提升。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值