一.数据库

1.数据库主键自增

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

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

1.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.2.通过 replace into 来插入数据。

BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;

插入数据这里,我们没有使用 insert into 而是使用 replace into 来插入数据,具体步骤是这样的:

1)第一步: 尝试把数据插入到表中。

2)第二步: 如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。

这种方式的优缺点也比较明显:

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

2.数据库号段模式

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

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

数据库的号段模式也是目前比较主流的一种分布式 ID 生成方式。像滴滴开源的Tinyidopen in new window 就是基于这种方式来做的。不过,TinyId 使用了双号段缓存、增加多 db 支持等方式来进一步优化。

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

2.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.2.先插入一行数据。

INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES(1, 0, 100, 0, 101);

2.3.通过 SELECT 获取指定业务下的批量唯一 ID

SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101

结果:

id   current_max_id  step    version biz_type
1   0   100 0   101

2.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   current_max_id  step    version biz_type
1   100 100 1   101

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

另外,为了避免单点问题,你可以从使用主从模式来提高可用性。

数据库号段模式的优缺点:

  • 优点 :ID 有序递增、存储消耗空间小
  • 缺点 :存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量)

3.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 集群方案Codisopen in new window (大规模集群比如上百个节点的时候比较推荐)。

除了高可用和并发之外,我们知道 Redis 基于内存,我们需要持久化数据,避免重启机器或者机器故障后数据丢失。Redis 支持两种不同的持久化方式:快照(snapshotting,RDB)只追加文件(append-only file, AOF)。 并且,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

Redis 方案的优缺点:

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

除了 Redis 之外,MongoDB ObjectId 经常也会被拿来当做分布式 ID 的解决方案。

MongoDB ObjectId 一共需要 12 个字节存储:

  • 0~3:时间戳
  • 3~6: 代表机器 ID
  • 7~8:机器进程 ID
  • 9~11 :自增值

MongoDB 方案的优缺点:

  • 优点 : 性能不错并且生成的 ID 是有序递增的
  • 缺点 : 需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID) 、有安全性问题(ID 生成有规律性)

二.算法

1.UUID

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

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

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

RFC 4122open in new window 中关于 UUID 的示例是这样的:

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

5 种不同的 Version(版本)值分别对应的含义(参考维基百科对于 UUID 的介绍open in new window):

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

下面是 Version 1 版本下生成的 UUID 的示例:

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

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

另外,Variant(变体)也有 4 种不同的值,这种值分别对应不同的含义。这里就不介绍了,貌似平时也不怎么需要关注。

需要用到的时候,去看看维基百科对于 UUID 的 Variant(变体) 相关的介绍即可。

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

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

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

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

最后,我们再简单分析一下 UUID 的优缺点 (面试的时候可能会被问到的哦!) :

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

2.Snowflake(雪花算法)

Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:

  • 第 0 位: 符号位(标识正负),始终为 0,没有用,不用管。
  • 第 1~41 位 :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
  • 第 42~52 位 :一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
  • 第 53~64 位 :一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。

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

另外,在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。

我们再来看看 Snowflake 算法的优缺点 :

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

三.开源框架

1.UidGenerator(百度)

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

不过,UidGenerator 对 Snowflake(雪花算法)进行了改进,生成的唯一 ID 组成如下。

可以看出,和原始 Snowflake(雪花算法)生成的唯一 ID 的组成不太一样。并且,上面这些参数我们都可以自定义。

UidGenerator 官方文档中的介绍如下:

自 18 年后,UidGenerator 就基本没有再维护了,我这里也不过多介绍。想要进一步了解的朋友,可以看看 UidGenerator 的官方介绍open in new window。

2.Leaf(美团)

Leafopen in new window 是美团开源的一个分布式 ID 解决方案 。这个项目的名字 Leaf(树叶) 起源于德国哲学家、数学家莱布尼茨的一句话: “There are no two identical leaves in the world”(世界上没有两片相同的树叶) 。这名字起得真心挺不错的,有点文艺青年那味了!

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

Leaf 的诞生主要是为了解决美团各个业务线生成分布式 ID 的方法多种多样以及不可靠的问题。

Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程。简单来说,就是我一个号段还没用完之前,我自己就主动提前去获取下一个号段(图片来自于美团官方文章:《Leaf——美团点评分布式 ID 生成系统》open in new window)。

根据项目 README 介绍,在 4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms。

3.Tinyid(滴滴)

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

数据库号段模式的原理我们在上面已经介绍过了。Tinyid 有哪些亮点呢?

为了搞清楚这个问题,我们先来看看基于数据库号段模式的简单架构方案。(图片来自于 Tinyid 的官方 wiki:《Tinyid 原理介绍》open in new window)

在这种架构模式下,我们通过 HTTP 请求向发号器服务申请唯一 ID。负载均衡 router 会把我们的请求送往其中的一台 tinyid-server。

这种方案有什么问题呢?在我看来(Tinyid 官方 wiki 也有介绍到),主要由下面这 2 个问题:

  • 获取新号段的情况下,程序获取唯一 ID 的速度比较慢。
  • 需要保证 DB 高可用,这个是比较麻烦且耗费资源的。

除此之外,HTTP 调用也存在网络开销。

Tinyid 的原理比较简单,其架构如下图所示:

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

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

参考文章:分布式 ID | JavaGuide

分布式ID常见解决方案总结相关推荐

  1. 【分布式篇】什么是分布式ID?分布式ID常见解决方案有哪些?

    目录 分布式ID 什么是分布式ID 分布式ID需要满足的需求 基本需求: 其他需求 分布式 ID 常见解决方案 1. 数据库维度 基于数据库主键自增实现 优点 : 缺点 : 基于数据库的号段模式实现( ...

  2. 分布式ID生成解决方案——推特雪花算法

    对于某些应用,MySQL里的某个表可能会占用很大的存储空间,甚至让服务器硬盘满了,这时候就会涉及到数据库的分片,把一个数据库进行拆分,通过数据库中间件(MyCat)进行连接. 假设现在我们有三台服务器 ...

  3. 分布式ID业界解决方案

    分布式ID 分布式 ID 唯一身份标识 类比身份证,引出分布式ID: AtomicInteger / AtomicLong 线程对外看是唯一的 资源征用的场景如何保证唯一:锁 乐观锁 / 悲观锁 解决 ...

  4. 一致 先验分布 后验分布_分布式事务常见解决方案与最终一致性

    小编推荐:互联网大背景下,微服务盛行,平时开发中难免会遇到分布式事务问题.大家经常会听到CAP原理,即一致性(Consistency).可用性(Availability).分区容错性(Partitio ...

  5. 分布式事务常见解决方案

    分布式事务的目的是保障分布式存储中数据一致性,而跨库事务会遇到各种不可控制的问题,如个别节点宕机,像单机事务一样的ACID是无法奢望的 一. 二阶段提交(2PC)(XA Transactions) 在 ...

  6. 分布式事务-常见解决方案

    2PC模式 (XA Transactions) 核心:事务协调器,协调是否提交和回滚 2PC(2 phrase commit 二阶段提交),又叫做:XA Transactions MySQL从5.5版 ...

  7. 分布式事务常见解决方案-关于状态机

    在使用最终一致性的方案时,一定要提到的一个概念是状态机. 什么是状态机?是一种特殊的组织代码的方式,用这种方式能够确保你的对象随时都知道自己所处的状态以及所能做的操作.它也是一种用来进行对象行为建模的 ...

  8. 分布式理论分布式ID生成大全

    分布式 ID 介绍 一.何为 ID? 日常开发中,我们需要对系统中的各种数据使用 ID 唯一表示,比如用户 ID 对应且仅对应一个人,商品 ID 对应且仅对应一件商品,订单 ID 对应且仅对应一个订单 ...

  9. 美团 Leaf分布式ID解决方案

    前言 看了一下美团的分布式ID的解决方案,谈谈自己的理解和思考.其中参考博客就是美团的分布式ID leaf的链接,可以直接跳转去看. Leaf-segment 数据库方案 这里采用的是从数据库读取,每 ...

最新文章

  1. 读“基于深度学习的图像识别技术研究综述”有感
  2. Objective-C中的常量
  3. 设计模式 — 结构型模式 — 外观模式
  4. 华为云RTC服务架构及应用实践
  5. 实现pxe的自动化安装
  6. 做玫瑰花的方法 用纸_新生活新健康:春饮玫瑰花茶 最是疏肝解郁
  7. c语言用if如何删除末尾空格,新人提问:如何将输出时每行最后一个空格删除
  8. 栈的出入js实现_js 实现栈的结构
  9. 排序算法专题-冒泡排序
  10. html怎么打出黑方块,方块怎么打,特殊符号黑方块
  11. Python版解决中文字符串错误
  12. B2B供应链管理平台主流技术架构方案
  13. 什么是Map Reduce
  14. Linux系列课程之一Linux的介绍
  15. 租房需要注意些什么?
  16. 编译原理--实验2 语法分析
  17. Vivado HLS 入门实验
  18. 【AI TOP 10】马化腾:AI技术沦为网络黑产新工具;网易区块链项目被传夭折; 人工智能可以让狗跟人说话...
  19. android安全学习之2—android中.pem和.pk8是什么文件?
  20. 单源广度优先搜索 (leetcode经典例题 C++实现)

热门文章

  1. 邮箱发送验证码实现注册(QQ邮箱)
  2. 微信小程序之多行文本省略号
  3. 微信公众号找券搜券和返利机器人的详细配置教程分享
  4. 本人擅长Ai、Fw、Fl、Br、Ae、Pr、Id、Ps等
  5. visio连接线随形状移动自动伸缩
  6. 3 FFmpeg从入门到精通-FFmpeg转封装
  7. 盒子模型和怪异盒子模型
  8. CreateChildControls、EnsureChildControls、RenderControl、Render、RenderChildren
  9. java游戏为什么_手机Java游戏老玩家:为什么我开始了玩王者荣耀?
  10. 海子Java并发编程学习总结