当前位置:   article > 正文

常见分布式ID解决方案

常见分布式ID解决方案

简介: 分布式ID解决方案是用于在分布式系统中生成唯一标识符的方案。常见的分布式ID解决方案可总结为3点:数据库方案、算法方案、开源组件方案。

分布式ID

分布式 ID(Distributed ID)是指在分布式系统中生成全局唯一的标识符,用于标识不同实体或数据对象。在分布式系统中,由于数据存储、计算和处理都分散在不同的节点上,因此需要一个可靠的方式来跟踪和标识这些数据对象。

分布式ID最低要求:

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

优秀的分布式 ID

  1. 安全 :ID 中不包含敏感信息
  2. 有序递增 :如果ID存放在数据库,ID的有序性可以提升数据库写入速度。有利于ID来进行排序
  3. 有具体的业务含义 :生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)
  4. 独立部署 :分布式系统单独有一个发号器服务,专门用来生成分布式 ID

分布式ID方案之数据库

数据库主键自增

数据库自增ID是在数据库中创建表时,通过设置一个自增的ID字段来实现的。每当插入一条记录时,数据库会自动为该记录生成一个唯一的ID。

数据库自增ID可以很好地保证ID的唯一性,但在高并发和大规模的分布式系统中,容易出现瓶颈和性能问题。同时,由于数据库自增ID只能在单个数据库中保证唯一性,因此需要通过分库分表等方式来支持多台机器上的生成。

简言之:

  1. 简单方便,有序递增,方便排序和分页
  2. 并发性能不高,受限于数据库性能
  3. 分库分表,需改造,较复杂
  4. 自增数据量泄露

数据库号段模式

数据库主键自增这种模式,每次获取 ID 都要访问一次数据库,数据库压力大。因此,可以批量获取,然后存在内存里面,需要用到的时候,直接从内存里面拿来使用

主键自增

1,2,3......

号段模式:每请求一次分配一个号段

  1. 100,200,300
  2. 1...100,101...200,201...300

号段模式相比主键自增而言: 性能提高且自增

Redis自增

Redis 可以通过自增命令来实现分布式 ID 的生成。常用的方法是使用 Redis 的自增命令 INCR,将一个特定的 key 自增,并将其作为 ID 返回。这种方法是线程安全的,可以在分布式系统中使用

  1. 即使有AOF和RDB,但是依然会存在数据丢失的可能,有可能会造成ID重复
  2. 性能不错并且生成的 ID 是有序递增的,但是自增存在数据量泄露

MongoDB

MongoDB ObjectId是MongoDB数据库中的一个内置数据类型,用于唯一标识MongoDB文档(Document)。

它由12个字节组成,其中前4个字节表示时间戳,接下来3个字节表示机器ID,然后2个字节表示进程ID,最后3个字节表示随机值。

优缺点:

  1. 生成的 ID 是有序递增的
  2. 当机器时间不对的情况下,可能导致会产生重复 ID
  3. ID生成有规律性,存在安全性问题

分布式ID方案之算法

UUID

UUID是一种通用唯一识别码,它是由一组算法和标准组成的,可以保证在全球范围内唯一性。UUID不依赖于任何中心节点,可以在分布式系统中很好地保证ID的唯一性。缺点是它生成的ID比较长,不利于索引和查询

开放软件基金会(OSF)规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素。利用这些元素来生成UUID。

优缺点:

  1. 通过本地生成,没有经过网络I/O,性能较快
  2. 无序,无法预测他的生成顺序
  3. 存储消耗空间大(32 个字符串,128 位)
  4. 不能生成递增有序的数字
  5. 当机器时间不对的情况下,可能导致会产生重复 ID

Snowflake(雪花算法)

雪花算法是 Twitter 提出的一种分布式ID生成算法。雪花算法可以在多台机器上生成不重复的ID,支持高并发和大规模的分布式系统,但需要保证数据中心ID和机器ID的唯一性。

它的原理是将一个64位的long类型的ID分为4个部分:时间戳、数据中心ID、机器ID和序列号。

时间戳占用了42位,可以使用69年,数据中心ID和机器ID分别占用了5位,可以支持32个数据中心和32个机器,序列号占用了12位,可以支持每个节点每毫秒生成4096个ID。

细一点说:生成的64位ID可以分成5个部分:

1位符号位标识 - 41位时间戳 - 5位数据中心标识 - 5位机器标识 - 12位序列号

image.png

时间范围

2^41/(365*24*60*60*1000)=69

工作进程数量

  1. 5+5 :区域+服务器标识
  2. 2^10=1024

序列号数量

2^12=4096
分段作用说明
1bit保留不用long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1
41bit时间戳,精确到毫秒存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年
5bit数据中心最多支持2的5次方(32)个节点
5bit机器id最多支持2的5次方(32)个节点
12bit毫秒内的计数器每个节点每毫秒最多产生2的12次方(4096)个id

默认情况下41bit的时间戳可以支持该算法使用到2082年,10bit的工作机器id可以支持1024台机器,序列号支持1毫秒产生4096个自增序列id 。SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右

优缺点:

  1. 生成速度比较快、生成的 ID 有序递增、比较灵活
  2. 依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID

雪花算法的使用

IdWorker工具类

  1. /**
  2. * Twitter的Snowflake JAVA实现方案
  3. * 分布式自增长ID
  4. */
  5. public class IdWorker {
  6. // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
  7. private final static long twepoch = 1288834974657L;
  8. // 机器标识位数
  9. private final static long workerIdBits = 5L;
  10. // 数据中心标识位数
  11. private final static long datacenterIdBits = 5L;
  12. // 机器ID最大值
  13. private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
  14. // 数据中心ID最大值
  15. private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
  16. // 毫秒内自增位
  17. private final static long sequenceBits = 12L;
  18. // 机器ID偏左移12
  19. private final static long workerIdShift = sequenceBits;
  20. // 数据中心ID左移17
  21. private final static long datacenterIdShift = sequenceBits + workerIdBits;
  22. // 时间毫秒左移22
  23. private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
  24. private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
  25. /* 上次生产id时间戳 */
  26. private static long lastTimestamp = -1L;
  27. // 0,并发控制
  28. private long sequence = 0L;
  29. private final long workerId;
  30. // 数据标识id部分
  31. private final long datacenterId;
  32. public IdWorker() {
  33. this.datacenterId = getDatacenterId(maxDatacenterId);
  34. this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
  35. }
  36. /**
  37. * @param workerId 工作机器ID
  38. * @param datacenterId 序列号
  39. */
  40. public IdWorker(long workerId, long datacenterId) {
  41. if (workerId > maxWorkerId || workerId < 0) {
  42. throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
  43. }
  44. if (datacenterId > maxDatacenterId || datacenterId < 0) {
  45. throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
  46. }
  47. this.workerId = workerId;
  48. this.datacenterId = datacenterId;
  49. }
  50. /**
  51. * 获取下一个ID
  52. *
  53. * @return
  54. */
  55. public synchronized long nextId() {
  56. long timestamp = timeGen();
  57. if (timestamp < lastTimestamp) {
  58. throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
  59. }
  60. if (lastTimestamp == timestamp) {
  61. // 当前毫秒内,则+1
  62. sequence = (sequence + 1) & sequenceMask;
  63. if (sequence == 0) {
  64. // 当前毫秒内计数满了,则等待下一秒
  65. timestamp = tilNextMillis(lastTimestamp);
  66. }
  67. } else {
  68. sequence = 0L;
  69. }
  70. lastTimestamp = timestamp;
  71. // ID偏移组合生成最终的ID,并返回ID
  72. long nextId = ((timestamp - twepoch) << timestampLeftShift)
  73. | (datacenterId << datacenterIdShift)
  74. | (workerId << workerIdShift) | sequence;
  75. return nextId;
  76. }
  77. private long tilNextMillis(final long lastTimestamp) {
  78. long timestamp = this.timeGen();
  79. while (timestamp <= lastTimestamp) {
  80. timestamp = this.timeGen();
  81. }
  82. return timestamp;
  83. }
  84. private long timeGen() {
  85. return System.currentTimeMillis();
  86. }
  87. /**
  88. * <p>
  89. * 获取 maxWorkerId
  90. * </p>
  91. */
  92. protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
  93. StringBuffer mpid = new StringBuffer();
  94. mpid.append(datacenterId);
  95. String name = ManagementFactory.getRuntimeMXBean().getName();
  96. if (!name.isEmpty()) {
  97. /*
  98. * GET jvmPid
  99. */
  100. mpid.append(name.split("@")[0]);
  101. }
  102. /*
  103. * MAC + PID 的 hashcode 获取16个低位
  104. */
  105. return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
  106. }
  107. /**
  108. * <p>
  109. * 数据标识id部分
  110. * </p>
  111. */
  112. protected static long getDatacenterId(long maxDatacenterId) {
  113. long id = 0L;
  114. try {
  115. InetAddress ip = InetAddress.getLocalHost();
  116. NetworkInterface network = NetworkInterface.getByInetAddress(ip);
  117. if (network == null) {
  118. id = 1L;
  119. } else {
  120. byte[] mac = network.getHardwareAddress();
  121. id = ((0x000000FF & (long) mac[mac.length - 1])
  122. | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
  123. id = id % (maxDatacenterId + 1);
  124. }
  125. } catch (Exception e) {
  126. System.out.println(" getDatacenterId: " + e.getMessage());
  127. }
  128. return id;
  129. }
  130. public static void main(String[] args) {
  131. IdWorker idWorker = new IdWorker(0, 0);
  132. for (int i = 0; i < 10000; i++) {
  133. long nextId = idWorker.nextId();
  134. System.out.println(nextId);
  135. }
  136. }
  137. }

配置分布式ID生成器

application.ym添加配置

  1. workerId: 0
  2. datacenterId: 0

IdWorker添加到容器

  1. @Value("${workerId}")
  2. private Integer workerId;
  3. @Value("${datacenterId}")
  4. private Integer datacenterId;
  5. @Bean
  6. public IdWorker idWorker(){
  7. return new IdWorker(workerId,datacenterId);
  8. }

分布式ID方案之开源组件

uid- generator(百度)

UidGenerator是百度开源的一款基于 Snowflake的唯一 ID 生成器,是对 Snowflake进行了改进

GitHub:https://github.com/baidu/uid-generator

image.png

Tinyid(滴滴)

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

GitHub: https://github.com/didi/tinyid

image.png

Leaf(美团)

Leaf是美团开源的一个分布式 ID 解决方案。提供了号段模式 和 Snowflake这两种模式来生成分布式 ID。

目前Leaf覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM基础上,通过公司RPC方式调用,QPS压测结果近5w/s,TP999 1ms。

Leaf 设计文档: https://tech.meituan.com/2017/04/21/mt-leaf.html

GitHub:https://github.com/meituan-diaNPing/leaf

三者比较

  1. 百度:只支持雪花算法
  2. 滴滴:只支持数据库号段,多DB,高可用,java- client,适合对id有高可用需求
  3. 美团:号段模式和 snowflake模,适合多种场景分布式id

Leaf组件的使用

源码打包

  1. git clone git@github.com:Meituan-Dianping/Leaf.git
  2. cd Leaf
  3. git checkout feature/spring-boot-starter
  4. mvn clean install -Dmaven.test.skip=true

引入依赖

目前Leaf最新使用2.0.1.RELEASE的starter版本
  1. <parent>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-parent</artifactId>
  4. <version>2.0.1.RELEASE</version>
  5. </parent>
  6. <dependencies>
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-web</artifactId>
  10. </dependency>
  11. <!--引入源码编译打包安装到本地的Leaf-->
  12. <dependency>
  13. <artifactId>leaf-boot-starter</artifactId>
  14. <groupId>com.sankuai.inf.leaf</groupId>
  15. <version>1.0.1-RELEASE</version>
  16. </dependency>
  17. <!--zk-->
  18. <dependency>
  19. <groupId>org.apache.curator</groupId>
  20. <artifactId>curator-recipes</artifactId>
  21. <version>2.6.0</version>
  22. <exclusions>
  23. <exclusion>
  24. <artifactId>log4j</artifactId>
  25. <groupId>log4j</groupId>
  26. </exclusion>
  27. </exclusions>
  28. </dependency>
  29. </dependencies>

Leaf配置参数

Leaf 提供两种生成的ID的方式(号段模式和snowflake模式),可以同时开启两种方式,也可以指定开启某种方式(默认两种方式为关闭状态)。

配置项含义默认值
leaf.nameleaf服务名
leaf.segment.enable是否开启号段模式false
leaf.jdbc.urlmysql 库地址
leaf.jdbc.usernamemysql 用户名
leaf.jdbc.passwordmysql 密码
leaf.snowflake.enable是否开启snowflake模式false
leaf.snowflake.zk.addresssnowflake模式下的zk地址
leaf.snowflake.portsnowflake模式下的服务注册端口

号段模式配置

  1. 如果使用号段模式,需要建立DB表,并配置leaf.jdbc.url, leaf.jdbc.username, leaf.jdbc.password
  2. 如果不想使用该模式配置leaf.segment.enable=false即可。
  1. CREATE DATABASE leaf
  2. CREATE TABLE `leaf_alloc` (
  3. `biz_tag` varchar(128) NOT NULL DEFAULT '',
  4. `max_id` bigint(20) NOT NULL DEFAULT '1',
  5. `step` int(11) NOT NULL,
  6. `description` varchar(256) DEFAULT NULL,
  7. `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  8. PRIMARY KEY (`biz_tag`)
  9. ) ENGINE=InnoDB;
  10. insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 2000, 'Test leaf Segment Mode Get Id')

在classpath下配置leaf.properties

  1. leaf.name=com.sankuai.leaf.opensource.test
  2. leaf.segment.enable=true
  3. leaf.segment.url=jdbc:mysql://127.0.0.1:3306/leaf
  4. leaf.segment.username=root
  5. leaf.segment.password=123456

Snowflake模式配置

算法取自twitter开源的snowflake算法。如果不想使用该模式配置leaf.snowflake.enable=false即可。

在classpath下配置leaf.properties

在leaf.properties中配置leaf.snowflake.zk.address,配置leaf 服务监听的端口leaf.snowflake.port。
  1. leaf.snowflake.enable=true
  2. leaf.snowflake.address=127.0.0.1
  3. leaf.snowflake.port=2181
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/301913
推荐阅读
  

闽ICP备14008679号