5 分布式ID

这里讲一个比较常用的分布式防重复的ID生成策略,雪花算法

一个用户体量比较大的分布式系统必然伴随着分表分库,分机房部署,单体的部署方式肯定是承载不了这么大的体量。

雪花算法的结构说明

如下图所示:

雪花算法组成

从上图我们可以看出来雪花算法是64bit位的long类型的数值型的id。其中由4部分组成。

1bit为固定为0,表明生成的id为正数。

41bit位毫秒级的时间戳(可保留69年的时间戳)

标识位为10位,由5位的机器id和5位的服务id组成。5bit位能标识32个数值,32*32=1024 共能表示1024个数值,也就是说我们部署的服务可以水平扩展至1024个实例部署。在国内的应用中基本上是够用了。

12位的序列号位,表示同一个实例在1毫秒内能生成4096个序列,基本上也是够用了。

如果按照水平扩展应用还不能够承载我们的体量,我们可以对上面的组成进行改造。比如我们一个应用实例不会在1毫秒生成4096个数值,我们可以把时间戳的值改成35位,把6个bit均分到机器id和服务id上,也就是说我们可以部署256*256=65536个实例。

雪花算法的具体实现代码如下:

复制代码
/**
 * 雪花算法工具类
 * 64位long类型的= 第一位0表示为正数 + 1到41的时间戳 + 5位的数据中心id + 5位的机器id + 12位的序列号
 * @author yusong
 * @20241111
 */
public class SnowflakeIdWorkerUtil {
      
      //开始时间戳 2025-01-01=1735660800000L
      private final long startTime = 1731400000000L;
      //数据中心所占雪花算法数据的位数
      private final long datacenterIdBits = 5L;
      //机器号所占雪花算法的位数
      private final long workerIdBits = 5L;
//    //支持最大的数据中心 结果为31
//    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
//    //支持最大的机器编号 结果为31
//    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
      //同一时间戳生成的序列号所占位数
      private final long sequenceBits = 12L;
      //机器号左移位数=序列号的位数 = 12
      private final long workerIdShift = sequenceBits;
      //数据中心左移位数= 序列号的位数 + 机器号的位数 = 17
      private final long datacenterIdShift = sequenceBits + workerIdBits;
      //时间戳左移位数= 序列号的位数 + 机器号的位数 + 数据中心位数 = 22
      private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
      //生成序列号的最大数=4095
      private final long sequenceMask = -1L ^ (-1L << sequenceBits);
      //数据中心编号
      private long datacenterId;
      //机器号
      private long workerId;
      //毫秒内序列从0开始
      private long sequence = 0L;
      //上次生成ID的时间截
      private long lastTimestamp = -1L;
      
      
      private static SnowflakeIdWorkerUtil sw = null;
      
      /**
       *
       * @param datacenterId 数据中心编号id
       * @param workerId           机器编号id
       * @return
       */
      public static synchronized SnowflakeIdWorkerUtil getSnowflakeId(long datacenterId,long workerId) {
            
            if(datacenterId>31||datacenterId<0) {
                  throw new RuntimeException("datacenterId必须是0到31位的整数");
            }
            if(workerId>31||workerId<0) {
                  throw new RuntimeException("workerId必须是0到31位的整数");
            }
            if(sw==null) {
                  sw = new SnowflakeIdWorkerUtil();
                  sw.datacenterId = datacenterId;
                  sw.workerId = workerId;
            }
            
            return sw;
      }
      
      /**
       * 私有的构造函数 防止外部new
       */
      private SnowflakeIdWorkerUtil() {}
      
      /**
       * 获取下一个id
       * @return
       */
      public synchronized long nextId() {
            
            long timestamp = timeGen();
            //如果当前时间戳小于上次的时间戳 说明系统时钟回退过 应该抛出异常
            if(timestamp<this.lastTimestamp) {
                  throw new RuntimeException("系统时钟回退异常,请检查系统设置");
            }
            //如果是同一时间戳生成id,则进行毫秒内序列
            if(this.lastTimestamp==timestamp) {
                  
                  sequence = (sequence + 1) & sequenceMask;
                  //同一毫秒内序列溢出 则等待下一秒
                  if(sequence==0) {
                        timestamp = nextMillis(lastTimestamp);
                  }
            }else {
                  sequence = 0L;
            }
            this.lastTimestamp = timestamp;
            
            return ((timestamp - startTime)<<timestampLeftShift)|(datacenterId<<datacenterIdShift)|(workerId<<workerIdShift)|sequence;
      }
      
      /**
       * 阻塞到下一毫秒,直到获取到新的时间戳
       * @param lastTimestamp
       * @return
       */
      private long nextMillis(long lastTimestamp) {
            
            long timestamp = timeGen();
            while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
            return timestamp;
      }
      
      /**
       * 获取当前的时间戳
       * @return
       */
      private long timeGen() {
            return System.currentTimeMillis();
      }
      
      
      public static void main(String[] args) {
            
            
            for(int i=0;i<100;i++) {
                  SnowflakeIdWorkerUtil swu = SnowflakeIdWorkerUtil.getSnowflakeId(0, 0);
                  long id = swu.nextId();
                  System.out.println(Long.toBinaryString(id));
                  System.out.println(id);
            }
      }
      
}

雪花算法有个问题就是不能进行时钟回拨,如果进行时钟回拨就会生成重复的id。如果我们考虑以上问题可以在我们的程序中保留最近一次的生成时间,并拿来做校验,就可解决这个问题。办法总比问题多。

相关推荐
Greedy Alg2 小时前
LeetCode 239. 滑动窗口最大值
数据结构·算法·leetcode
空白到白3 小时前
机器学习-KNN算法
人工智能·算法·机器学习
闪电麦坤953 小时前
数据结构:排序算法的评判标准(Criteria Used For Analysing Sorts)
数据结构·算法·排序算法
爱coding的橙子3 小时前
每日算法刷题Day65:8.27:leetcode dfs11道题,用时2h30min
算法·leetcode·深度优先
不懂机器人3 小时前
linux网络编程-----TCP服务端并发模型(epoll)
linux·网络·tcp/ip·算法
青云交4 小时前
Java 大视界 -- 基于 Java 的大数据实时流处理在智能电网分布式电源接入与电力系统稳定性维护中的应用(404)
java·大数据·分布式·智能电网·flink 实时流处理·kafka 数据采集·iec 61850 协议
地平线开发者4 小时前
理想汽车智驾方案介绍 3|MoE+Sparse Attention 高效结构解析
算法·自动驾驶
小O的算法实验室5 小时前
2025年KBS SCI1区TOP,矩阵差分进化算法+移动网络视觉覆盖无人机轨迹优化,深度解析+性能实测
算法·论文复现·智能算法改进
艾莉丝努力练剑7 小时前
【C语言16天强化训练】从基础入门到进阶:Day 11
c语言·学习·算法
浩少7028 小时前
LeetCode-22day:多维动态规划
算法·leetcode·动态规划