分布式id生成算法之:雪花算法
雪花算法概述
雪花算法是由4个部分组合而成的:符号位+41位时间戳+10位机器码+12位序列号。
代码块
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> 0 - 00000000 00000000 00000000 00000000 0 - 00000000 00 - 00000000 0000</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> 1bit不用 中间41bit是时间戳 10bit是机器码 12bit为序列号</span></span></span></span>
这么组合的好处是:
-
全局唯一:在分布式 部署的环境下,相同机器上,不同机器之间,不能出现重复ID。
-
数据安全:如果涉及到如订单号类的ID透出诉求,则需要考虑用非连续ID来隐藏生产状况。
源码分析
定义ID组合方式所需的常量
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 开始时间 (2020-05-03) */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long twepoch = 1607529600000L;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 机器码所占的bit位数 = 10位 */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long workerIdBits = 10L;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 最大支持的机器码 = 1023,该式子相当与~(-1L << 10L) */ </span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long maxWorkerId = -1L ^ (-1L << workerIdBits);</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 序列号所占的bit位数 */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long sequenceBits = 12L;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 机器码需要在序列号的左边 */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long workerIdShift = sequenceBits;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 时间戳的起始位置在序列号和机器码的左边 */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long timestampLeftShift = sequenceBits + workerIdBits;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 序列号的最大支持数值 */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private final long sequenceMask = -1L ^ (-1L << sequenceBits);</span></span></span></span>
定义三个位段的含义字段
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 机器码 (0 ~ 1023) */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private long workerId;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 序列号 (0 ~ 4095) */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private long sequence = 0L;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"></span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">/* 最后时间戳 */</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">private long lastTimestamp = -1L;</span></span></span></span>
构建机器码
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">InetAddress address;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">try {</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> //获取本机IP</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> address = InetAddress.getLocalHost();</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">} catch (final UnknownHostException e) {</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!",e);</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">}</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">byte[] ipAddressByteArray = address.getAddress();</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">//机器码一共需要10位,所以取段倒数第二个段取最后2位 + 倒数第一段全部8位</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">return ((ipAddressByteArray[ipAddressByteArray.length - 2] & 0B11) << Byte.SIZE) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF);</span></span></span></span>
代码块
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">//当前时间戳</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">long timestamp = System.currentTimeMillis()</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">//如果当前时间戳 < 最后记录时间戳 ,则可能发生时钟回拨,异常中断</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">if (timestamp < lastTimestamp) {</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> throw new RuntimeException(String.format(</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> "clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">}</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">//如果当前时间戳 == 最后记录时间戳 </span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">if (lastTimestamp == timestamp) {</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> //计算下一个序列号,这里&4095的作用是循环,因为到4096会变成0</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> sequence = (sequence + 1) & sequenceMask;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> //如果下一个序列号==0,则说明同一时间戳内序列号以用完,设置下一时间戳为最后时间戳</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> if (sequence == 0) {</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> //该方法内部while循环直到当前时间>最后时间 </span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> timestamp = tilNextMillis(lastTimestamp);</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> }</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">} else {</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000"> sequence = 0L;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">}</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">lastTimestamp = timestamp;</span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">//时间戳和指定时间的差 左移 12+10 | 机器码 左移12 | 序列号 </span></span></span></span>
<span style="background-color:#fcfcfc"><span style="color:#333333"><span style="background-color:#fcfcfc"><span style="color:#000000">return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;</span></span></span></span>