前言

在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一下,不一定全部适合,这些解决方案仅供你参考,或许对你有用。

正文

分布式ID的特性

  • 唯一性:确保生成的ID是全网唯一的。
  • 有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
  • 高可用性:确保任何时候都能正确的生成ID。
  • 带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。

分布式ID的生成方案

1. UUID

算法的核心思想是结合机器的网卡、当地时间、一个随记数来生成UUID。

  • 优点:本地生成,生成简单,性能好,没有高可用风险
  • 缺点:长度过长,存储冗余,且无序不可读,查询效率低

2. 数据库自增ID

使用数据库的id自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。

  • 优点:数据库生成的ID绝对有序,高可用实现方式简单
  • 缺点:需要独立部署数据库实例,成本高,有性能瓶颈

3. 批量生成ID

一次按需批量生成多个ID,每次生成都需要访问数据库,将数据库修改为最大的ID值,并在内存中记录当前值及最大值。

  • 优点:避免了每次生成ID都要访问数据库并带来压力,提高性能
  • 缺点:属于本地生成策略,存在单点故障,服务重启造成ID不连续

4. Redis生成ID

Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。

  • 优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排序的结果很有帮助。

  • 缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作量比较大。

考虑到单节点的性能瓶颈,可以使用 Redis 集群来获取更高的吞吐量。假如一个集群中有5台 Redis。可以初始化每台 Redis 的值分别是1, 2, 3, 4, 5,然后步长都是 5。各个 Redis 生成的 ID 为:

  1. A1, 6, 11, 16, 21
  2. B2, 7, 12, 17, 22
  3. C3, 8, 13, 18, 23
  4. D4, 9, 14, 19, 24
  5. E5, 10, 15, 20, 25
  6. 复制代码

随便负载到哪个机确定好,未来很难做修改。步长和初始值一定需要事先确定。使用 Redis 集群也可以方式单点故障的问题。

另外,比较适合使用 Redis 来生成每天从0开始的流水号。比如订单号 = 日期 + 当日自增长号。可以每天在 Redis 中生成一个 Key ,使用 INCR 进行累加。

5. Twitter的snowflake算法

Twitter 利用 zookeeper 实现了一个全局ID生成的服务 Snowflake:github.com/twitter/sno…

如上图的所示,Twitter 的 Snowflake 算法由下面几部分组成:

  • 1位符号位:

由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。

  • 41位时间戳(毫秒级):

需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41位毫秒时间戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年

  • 10位数据机器位:

包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署 1 << 10 = 1024 s个节点。超过这个数量,生成的ID就有可能会冲突。

  • 12位毫秒内的序列:

这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成 1 << 12 = 4096个ID

加起来刚好64位,为一个Long型。

  • 优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
  • 缺点:需要独立的开发和部署,依赖于机器的时钟

简单实现

  1. public class IdWorker {
  2. /**
  3. * 起始时间戳 2017-04-01
  4. */
  5. private final long epoch = 1491004800000L;
  6. /**
  7. * 机器ID所占的位数
  8. */
  9. private final long workerIdBits = 5L;
  10. /**
  11. * 数据标识ID所占的位数
  12. */
  13. private final long dataCenterIdBits = 5L;
  14. /**
  15. * 支持的最大机器ID,结果是31
  16. */
  17. private final long maxWorkerId = ~(-1L << workerIdBits);
  18. /**
  19. * 支持的最大数据标识ID,结果是31
  20. */
  21. private final long maxDataCenterId = ~(-1 << dataCenterIdBits);
  22. /**
  23. * 毫秒内序列在id中所占的位数
  24. */
  25. private final long sequenceBits = 12L;
  26. /**
  27. * 机器ID向左移12位
  28. */
  29. private final long workerIdShift = sequenceBits;
  30. /**
  31. * 数据标识ID向左移17(12+5)位
  32. */
  33. private final long dataCenterIdShift = sequenceBits + workerIdBits;
  34. /**
  35. * 时间戳向左移22(12+5+5)位
  36. */
  37. private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;
  38. /**
  39. * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
  40. */
  41. private final long sequenceMask = ~(-1L << sequenceBits);
  42. /**
  43. * 数据标识ID(0~31)
  44. */
  45. private long dataCenterId;
  46. /**
  47. * 机器ID(0~31)
  48. */
  49. private long workerId;
  50. /**
  51. * 毫秒内序列(0~4095)
  52. */
  53. private long sequence;
  54. /**
  55. * 上次生成ID的时间戳
  56. */
  57. private long lastTimestamp = -1L;
  58.  
  59. public IdWorker(long dataCenterId, long workerId) {
  60. if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
  61. throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId));
  62. }
  63. if (workerId > maxWorkerId || workerId < 0) {
  64. throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
  65. }
  66. this.dataCenterId = dataCenterId;
  67. this.workerId = workerId;
  68. }
  69.  
  70. /**
  71. * 获得下一个ID (该方法是线程安全的)
  72. * @return snowflakeId
  73. */
  74. public synchronized long nextId() {
  75. long timestamp = timeGen();
  76. //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常
  77. if (timestamp < lastTimestamp) {
  78. throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
  79. }
  80. //如果是同一时间生成的,则进行毫秒内序列
  81. if (timestamp == lastTimestamp) {
  82. sequence = (sequence + 1) & sequenceMask;
  83. //毫秒内序列溢出
  84. if (sequence == 0) {
  85. //阻塞到下一个毫秒,获得新的时间戳
  86. timestamp = nextMillis(lastTimestamp);
  87. }
  88. } else {//时间戳改变,毫秒内序列重置
  89. sequence = 0L;
  90. }
  91. lastTimestamp = timestamp;
  92. //移位并通过按位或运算拼到一起组成64位的ID
  93. return ((timestamp - epoch) << timestampShift) |
  94. (dataCenterId << dataCenterIdShift) |
  95. (workerId << workerIdShift) |
  96. sequence;
  97. }
  98.  
  99. /**
  100. * 返回以毫秒为单位的当前时间
  101. * @return 当前时间(毫秒)
  102. */
  103. protected long timeGen() {
  104. return System.currentTimeMillis();
  105. }
  106.  
  107. /**
  108. * 阻塞到下一个毫秒,直到获得新的时间戳
  109. * @param lastTimestamp 上次生成ID的时间截
  110. * @return 当前时间戳
  111. */
  112. protected long nextMillis(long lastTimestamp) {
  113. long timestamp = timeGen();
  114. while (timestamp <= lastTimestamp) {
  115. timestamp = lastTimestamp;
  116. }
  117. return timestamp;
  118. }
  119. }

6. 百度UidGenerator

UidGenerator是百度开源的分布式ID生成器,基于于snowflake算法的实现,看起来感觉还行。不过,国内开源的项目维护性真是担忧。

具体可以参考官网说明:github.com/baidu/uid-g…

7. 美团Leaf

Leaf 是美团开源的分布式ID生成器,能保证全局唯一性、趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。

具体可以参考官网说明:tech.meituan.com/MT_Leaf.htm…

小结

这篇文章和大家分享了全局id生成服务的几种常用方案,同时对比了各自的优缺点和适用场景。在实际工作中,大家可以结合自身业务和系统架构体系进行合理选型。

作者:陌上轩客
链接:https://juejin.im/post/5b3a23746fb9a024e15cad79
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

分布式唯一ID的几种生成方案的更多相关文章

  1. 分布式全局ID的几种生成方案

    前言 在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID.退款ID等. 那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是 ...

  2. 分布式唯一ID生成方案是什么样的?(转)

    一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...

  3. 一线大厂的分布式唯一ID生成方案是什么样的?

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  4. 分布式唯一ID的生成方案

    分布式ID的特性 全局唯一 不能出现重复的ID,这是最基本的要求. 递增 有利于关系数据库索引性能. 高可用 既然是服务于分布式系统,为多个服务提供ID服务,访问压力一定很大,所以需要保证高可用. 信 ...

  5. 分布式唯一ID生成方案选型!详细解析雪花算法Snowflake

    分布式唯一ID 使用RocketMQ时,需要使用到分布式唯一ID 消息可能会发生重复,所以要在消费端做幂等性,为了达到业务的幂等性,生产者必须要有一个唯一ID, 需要满足以下条件: 同一业务场景要全局 ...

  6. 分布式唯一ID生成算法-雪花算法

    在我们的工作中,数据库某些表的字段会用到唯一的,趋势递增的订单编号,我们将介绍两种方法,一种是传统的采用随机数生成的方式,另外一种是采用当前比较流行的“分布式唯一ID生成算法-雪花算法”来实现. 一. ...

  7. 开源项目|Go 开发的一款分布式唯一 ID 生成系统

    原文连接: 开源项目|Go 开发的一款分布式唯一 ID 生成系统 今天跟大家介绍一个开源项目:id-maker,主要功能是用来在分布式环境下生成唯一 ID.上周停更了一周,也是用来开发和测试这个项目的 ...

  8. php 生成唯一id的几种解决方法

    php 生成唯一id的几种解决方法   网上查了下,有很多的方法 1.md5(time() . mt_rand(1,1000000)); 这种方法有一定的概率会出现重复 2.php内置函数uniqid ...

  9. 分布式唯一id:snowflake算法思考

    匠心零度 转载请注明原创出处,谢谢! 缘起 为什么会突然谈到分布式唯一id呢?原因是最近在准备使用RocketMQ,看看官网介绍: 一句话,消息可能会重复,所以消费端需要做幂等.为什么消息会重复后续R ...

随机推荐

  1. vue+element ui 的时间控件选择 年月日时分

    前言:工作中用到 vue+element ui 的前端框架,需要选择年月日时分,但element ui官网demo有没有,所以记录一下.转载请注明出处:https://www.cnblogs.com/ ...

  2. Hibernate(二)持久化对象的状态

    简介 以前学习Hibernate的笔记,整理一下便发出来了,防止弄丢.有错误的话麻烦各位留言评论,感激不尽. 持久化类 Hibernate完成了从面向对象模型表示的对象至关系模型表示的数据结构的映射, ...

  3. CPU执行程序的原理(简化过程)

    前言 看了网上的一些描述CPU执行程序的过程,发现他们涉及到的内容太多了,恨不能把整个CPU的底层结构都拿出来说,这对计算机理论知识匮乏的新人甚至是一些老人都是非常不友好的.这个问题也是当初拦在我面前 ...

  4. PHP中常用的魔术方法

    我们在PHP中经常用到魔术方法,像构造方法,析构方法等等魔术变量,下面总结一下一些常用的魔术变量: __construct(),__destruct(),__clone(),__autoload(), ...

  5. ios移动端禁止双指缩放功能

    在实际开发中,我们禁止缩放的实现方式: 1.meta设置: <meta name="viewport"  content="width=device-width,h ...

  6. Android 蓝牙开发之搜索、配对、连接、通信大全

            蓝牙( Bluetooth®):是一种无线技术标准,可实现固定设备.移动设备和楼宇个人域网之间的短距离数据 交换(使用2.4-2.485GHz的ISM波段的UHF无线电波).蓝牙设备最 ...

  7. 网络I/O模型--06异步I/O

    异步I/O (又称为 AIO )则是采用“订阅一通知”工作模式 : 即应用程序向操作系统注册I/O监听,然后继续做自己的事情.当操作系统发生I/O事件,并且准备好数据后 , 再主动通知应用程序,触发相 ...

  8. hue简单介绍

    hue定义: HUE=Hadoop User Experience 个人理解:可视图的webui界面,方便大数据技术之间的CRUD操作. 官方定义:Hue是一个能够与Apache Hadoop交互的W ...

  9. Django基本设置

    Dango设置流程图片示例: 设置路由时需要注意的一个点是:Django中定义路由时,通常习惯以斜线 / 结尾 其他常用命令: python manage.py runserver 0.0.0.0:8 ...

  10. Windows 安装 python MySQLdb模块

    pip install wheel 去这个网站查找whl格式的MYSQL-python http://www.lfd.uci.edu/~gohlke/pythonlibs/#mysql-python ...