当前位置:   article > 正文

雪花算法:构建分布式自增id

雪花算法

目录

一、订单id的特殊性

二、雪花算法

三、简单原理

四、算法实现

五、配置

六、加载属性

七、编写配置类

八、使用

九、代码详解


一、订单id的特殊性

订单数据非常庞大,将来一定会做分库分表。那么这种情况下, 要保证id的唯一,就不能靠数据库自增,而是自己来实现算法,生成唯一id。

二、雪花算法

这里的订单id是通过一个工具类生成的:

而工具类所采用的生成id算法,是由Twitter公司开源的snowflake(雪花)算法。

三、简单原理

雪花算法会生成一个64位的二进制数据,为一个Long型。(转换成字符串后长度最多19位) ,其基本结构:

第一位:为未使用

第二部分:41位为毫秒级时间(41位的长度可以使用69年)

第三部分:5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)

第四部分:最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。

四、算法实现

  1. package com.leyou.utils;
  2. import java.lang.management.ManagementFactory;
  3. import java.net.InetAddress;
  4. import java.net.NetworkInterface;
  5. /**
  6. * <p>名称:IdWorker.java</p>
  7. * <p>描述:分布式自增长ID</p>
  8. * <pre>
  9. * Twitter的 Snowflake JAVA实现方案
  10. * </pre>
  11. * 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
  12. * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
  13. * 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
  14. * 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
  15. * 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
  16. * 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
  17. * 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
  18. * <p>
  19. * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
  20. *
  21. * @author Polim
  22. */
  23. public class IdWorker {
  24. /**
  25. * 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
  26. */
  27. private final static long twepoch = 1288834974657L;
  28. /**
  29. * 机器标识位数
  30. */
  31. private final static long workerIdBits = 5L;
  32. /**
  33. * 数据中心标识位数
  34. */
  35. private final static long datacenterIdBits = 5L;
  36. /**
  37. * 机器ID最大值
  38. */
  39. private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
  40. /**
  41. * 数据中心ID最大值
  42. */
  43. private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
  44. /**
  45. * 毫秒内自增位
  46. */
  47. private final static long sequenceBits = 12L;
  48. /**
  49. * 机器ID偏左移12位
  50. */
  51. private final static long workerIdShift = sequenceBits;
  52. /**
  53. * 数据中心ID左移17位
  54. */
  55. private final static long datacenterIdShift = sequenceBits + workerIdBits;
  56. /**
  57. * 时间毫秒左移22位
  58. */
  59. private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
  60. private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
  61. /**
  62. * 上次生产id时间戳
  63. */
  64. private static long lastTimestamp = -1L;
  65. /**
  66. * 并发控制
  67. */
  68. private long sequence = 0L;
  69. private final long workerId;
  70. /**
  71. * 数据标识id部分
  72. */
  73. private final long datacenterId;
  74. public IdWorker(){
  75. this.datacenterId = getDatacenterId(maxDatacenterId);
  76. this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
  77. }
  78. /**
  79. * @param workerId
  80. * 工作机器ID
  81. * @param datacenterId
  82. * 序列号
  83. */
  84. public IdWorker(long workerId, long datacenterId) {
  85. if (workerId > maxWorkerId || workerId < 0) {
  86. throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
  87. }
  88. if (datacenterId > maxDatacenterId || datacenterId < 0) {
  89. throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
  90. }
  91. this.workerId = workerId;
  92. this.datacenterId = datacenterId;
  93. }
  94. /**
  95. * 获取下一个ID
  96. *
  97. * @return
  98. */
  99. public synchronized long nextId() {
  100. long timestamp = timeGen();
  101. if (timestamp < lastTimestamp) {
  102. throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
  103. }
  104. if (lastTimestamp == timestamp) {
  105. // 当前毫秒内,则+1
  106. sequence = (sequence + 1) & sequenceMask;
  107. if (sequence == 0) {
  108. // 当前毫秒内计数满了,则等待下一秒
  109. timestamp = tilNextMillis(lastTimestamp);
  110. }
  111. } else {
  112. sequence = 0L;
  113. }
  114. lastTimestamp = timestamp;
  115. // ID偏移组合生成最终的ID,并返回ID
  116. long nextId = ((timestamp - twepoch) << timestampLeftShift)
  117. | (datacenterId << datacenterIdShift)
  118. | (workerId << workerIdShift) | sequence;
  119. return nextId;
  120. }
  121. private long tilNextMillis(final long lastTimestamp) {
  122. long timestamp = this.timeGen();
  123. while (timestamp <= lastTimestamp) {
  124. timestamp = this.timeGen();
  125. }
  126. return timestamp;
  127. }
  128. private long timeGen() {
  129. return System.currentTimeMillis();
  130. }
  131. /**
  132. * <p>
  133. * 获取 maxWorkerId
  134. * </p>
  135. */
  136. protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
  137. StringBuffer mpid = new StringBuffer();
  138. mpid.append(datacenterId);
  139. String name = ManagementFactory.getRuntimeMXBean().getName();
  140. if (!name.isEmpty()) {
  141. /*
  142. * GET jvmPid
  143. */
  144. mpid.append(name.split("@")[0]);
  145. }
  146. /*
  147. * MAC + PID 的 hashcode 获取16个低位
  148. */
  149. return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
  150. }
  151. /**
  152. * <p>
  153. * 数据标识id部分
  154. * </p>
  155. */
  156. protected static long getDatacenterId(long maxDatacenterId) {
  157. long id = 0L;
  158. try {
  159. InetAddress ip = InetAddress.getLocalHost();
  160. NetworkInterface network = NetworkInterface.getByInetAddress(ip);
  161. if (network == null) {
  162. id = 1L;
  163. } else {
  164. byte[] mac = network.getHardwareAddress();
  165. id = ((0x000000FF & (long) mac[mac.length - 1])
  166. | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
  167. id = id % (maxDatacenterId + 1);
  168. }
  169. } catch (Exception e) {
  170. System.out.println(" getDatacenterId: " + e.getMessage());
  171. }
  172. return id;
  173. }
  174. }

五、配置

为了保证不重复,我们给每个部署的节点都配置机器id:

六、加载属性

  1. package com.leyou.order.properties;
  2. import org.springframework.boot.context.properties.ConfigurationProperties;
  3. /**
  4. * @author: 98050
  5. * @create: 2018-10-27
  6. **/
  7. @ConfigurationProperties(prefix = "leyou.worker")
  8. public class IdWorkerProperties {
  9. /**
  10. * 当前机器id
  11. */
  12. private long workerId;
  13. /**
  14. * 序列号
  15. */
  16. private long dataCenterId;
  17. public long getWorkerId() {
  18. return workerId;
  19. }
  20. public void setWorkerId(long workerId) {
  21. this.workerId = workerId;
  22. }
  23. public long getDataCenterId() {
  24. return dataCenterId;
  25. }
  26. public void setDataCenterId(long dataCenterId) {
  27. this.dataCenterId = dataCenterId;
  28. }
  29. }

七、编写配置类

  1. package com.leyou.order.config;
  2. import com.leyou.order.properties.IdWorkerProperties;
  3. import com.leyou.utils.IdWorker;
  4. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. /**
  8. * @author: 98050
  9. * @create: 2018-10-27
  10. **/
  11. @Configuration
  12. @EnableConfigurationProperties(IdWorkerProperties.class)
  13. public class IdWorkerConfig {
  14. @Bean
  15. public IdWorker idWorker(IdWorkerProperties prop) {
  16. return new IdWorker(prop.getWorkerId(), prop.getDataCenterId());
  17. }
  18. }

八、使用

九、代码详解

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/470260
推荐阅读
相关标签
  

闽ICP备14008679号