分布式ID生成解决方案

た 入场券 2023-06-26 12:17 74阅读 0赞

分布式ID生成解决方案

1.1 UUID

这是常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。

  • 优点:
    1)简单,代码方便。
    2)生成ID性能非常好,基本不会有性能问题。
    3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
  • 缺点:
    1)没有排序,无法保证趋势递增。
    2)UUID往往是使用字符串存储,查询的效率比较低。
    3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
    4)传输数据量大
    5)不可读。

1.2 Redis

当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作INCR和INCRBY来实现。

  • 优点:
    1)不依赖于数据库,灵活方便,且性能优于数据库。

    2)数字ID天然排序,对分页或者需要排序的结果很有帮助。

  • 缺点:
    1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。

    ​ 2)需要编码和配置的工作量比较大。

    ​ 3)网络传输造成性能下降。

1.3 开源算法snowflake

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0

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

发表评论

表情:
评论列表 (有 0 条评论,74人围观)

还没有评论,来说两句吧...

相关阅读

    相关 分布式唯一ID生成方案

    ​ 在应用程序中,经常需要全局唯一的`ID`作为数据库主键。如何生成全局唯一ID? ​ 首先,需要确定全局唯一`ID`是整型还是字符串?如果是字符串,那么现有的`UUID`就

    相关 分布式Id生成方案

    系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结。生成ID的方法有很多,适应不同的场景、需求以及性能要求。所以有些比较复杂的系统会有多个ID生成的