SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。
这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。
给大家举个例子吧,比如下面那个 64 bit 的 long 型数字:
第一个部分是 1 个 bit:0,这个是无意义的,因为二进制中第一位是符号位,1表示负数,0表示正数。
第二个部分是 41 个 bit:表示的是时间戳。
第三个部分是 5 个 bit:表示的是机房 id,10001。
第四个部分是 5 个 bit:表示的是机器 id,1 1001。
第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000。
简单来说,你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。
这个 SnowFlake 算法系统首先肯定是知道自己所在的机房和机器的,比如机房 id = 17,机器 id = 12。
接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 id,64 个 bit 中的第一个 bit 是无意义的。
接着 41 个 bit,就可以用当前时间戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置上机器 id。
最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 id 的请求累加一个序号,作为最后的 12 个 bit。
雪花算法代码:
import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; /** * 雪花算法 */ public class SnowFlake { private final static long twepoch = 12888349746579L; // 机器标识位数 private final static long workerIdBits = 5L; // 数据中心标识位数 private final static long datacenterIdBits = 5L; // 毫秒内自增位数 private final static long sequenceBits = 12L; // 机器ID偏左移12位 private final static long workerIdShift = sequenceBits; // 数据中心ID左移17位 private final static long datacenterIdShift = sequenceBits + workerIdBits; // 时间毫秒左移22位 private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; //sequence掩码,确保sequnce不会超出上限 private final static long sequenceMask = -1L ^ (-1L << sequenceBits); //上次时间戳 private static long lastTimestamp = -1L; //序列 private long sequence = 0L; //服务器ID private long workerId = 1L; private static long workerMask = -1L ^ (-1L << workerIdBits); //进程编码 private long processId = 1L; private static long processMask = -1L ^ (-1L << datacenterIdBits); private static SnowFlake snowFlake = null; static{ snowFlake = new SnowFlake(); } public static synchronized long nextId(){ return snowFlake.getNextId(); } private SnowFlake() { //获取机器编码 this.workerId=this.getMachineNum(); //获取进程编码 RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); this.processId=Long.valueOf(runtimeMXBean.getName().split("@")[0]).longValue(); //避免编码超出最大值 this.workerId=workerId & workerMask; this.processId=processId & processMask; } public synchronized long getNextId() { //获取时间戳 long timestamp = timeGen(); //如果时间戳小于上次时间戳则报错 if (timestamp < lastTimestamp) { try { throw new Exception("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds"); } catch (Exception e) { e.printStackTrace(); } } //如果时间戳与上次时间戳相同 if (lastTimestamp == timestamp) { // 当前毫秒内,则+1,与sequenceMask确保sequence不会超出上限 sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { // 当前毫秒内计数满了,则等待下一秒 timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0; } lastTimestamp = timestamp; // ID偏移组合生成最终的ID,并返回ID long nextId = ((timestamp - twepoch) << timestampLeftShift) | (processId << datacenterIdShift) | (workerId << workerIdShift) | sequence; return nextId; } /** * 再次获取时间戳直到获取的时间戳与现有的不同 * @param lastTimestamp * @return 下一个时间戳 */ private long tilNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } /** * 获取机器编码 * @return */ private long getMachineNum(){ long machinePiece; StringBuilder sb = new StringBuilder(); Enumeration<NetworkInterface> e = null; try { e = NetworkInterface.getNetworkInterfaces(); } catch (SocketException e1) { e1.printStackTrace(); } while (e.hasMoreElements()) { NetworkInterface ni = e.nextElement(); sb.append(ni.toString()); } machinePiece = sb.toString().hashCode(); return machinePiece; } public static void main(String[] args) { new SnowFlake().getNextId(); } }
参考:https://blog.csdn.net/lq18050010830/article/details/89845790