参考资料:
GitHub - beyondfengyu/SnowFlake: Twitter的雪花算法SnowFlake,使用Java语言实现。
唯一ID工具-IdUtil (hutool.cn)
1. ID组成结构
1位,不用。 二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0。
41位,用来记录时间戳(毫秒) 。 * 41位可以表示$2^{41}-1$个数字 * 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 $2^{41}-1$,减1是因为可表示的数值范围是从0开始算的,而不是1。 * 也就是说41位可以表示$2^{41}-1$个毫秒的值,转化成单位年则是$(2^{41}-1) / (1000 * 60 * 60 * 24 * 365) = 69$年。
10位,用来记录工作机器id 。 * 可以部署在$2^{10} = 1024$个节点,包括 5位datacenterId 和 5位workerId。 * 5位(bit)可以表示的最大正整数是$2^{5}-1 = 31$,即可以用0、1、2、3、….31这32个数字,来表示不同的datecenterId或workerId。
12位,序列号,用来记录同毫秒内产生的不同id 。 * 12位(bit)可以表示的最大正整数是$2^{12}-1 = 4095$,即可以用0、1、2、3、….4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。
由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。
SnowFlake可以保证: ● 所有生成的id按时间趋势递增 ● 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
2. ID生成使用 Hutool工具中:
分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。Twitter的Snowflake 算法就是这种生成器。
使用方法如下:
1 2 3 Snowflake snowflake = IdUtil.getSnowflake(1 , 1 );long id = snowflake.nextId();
注意 IdUtil.createSnowflake
每次调用会创建一个新的Snowflake对象,不同的Snowflake对象创建的ID可能会有重复,因此请自行维护此对象为单例,或者使用IdUtil.getSnowflake
使用全局单例对象
。
网上的教程一般存在两个问题: 1. 机器ID(5位)和数据中心ID(5位)配置没有解决,分布式部署的时候会使用相同的配置,任然有ID重复的风险。 2. 使用的时候要实例化对象,没有形成开箱即用的工具类。
本文针对上面两个问题进行解决,笔者的解决方案是,workId使用服务器hostName生成,dataCenterId使用IP生成,这样可以最大限度防止10位机器码重复,但是由于两个ID都不能超过32,只能取余数,还是难免产生重复,但是实际使用中,hostName和IP的配置一般连续或相近,只要不是刚好相隔32位,就不会有问题,况且,hostName和IP同时相隔32的情况更加是几乎不可能的事,平时做的分布式部署,一般也不会超过10台容器。
使用上面的方法可以零配置使用雪花算法,雪花算法10位机器码的设定理论上可以有1024个节点,生产上使用docker配置一般是一次编译,然后分布式部署到不同容器,不会有不同的配置,这里不知道其他公司是如何解决的,即使有方法使用一套配置,然后运行时根据不同容器读取不同的配置,但是给每个容器编配ID,1024个(大部分情况下没有这么多),似乎也不太可能,此问题留待日后解决后再行补充。
具体生成 workId 和 dataCenterId 的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private static Long getWorkId () { try { String hostAddress = Inet4Address.getLocalHost().getHostAddress(); int [] ints = StringUtils.toCodePoints(hostAddress); int sums = 0 ; for (int b : ints){ sums += b; } return (long )(sums % 32 ); } catch (UnknownHostException e) { return RandomUtils.nextLong(0 ,31 ); } } private static Long getDataCenterId () { int [] ints = StringUtils.toCodePoints(SystemUtils.getHostName()); int sums = 0 ; for (int i: ints) { sums += i; } return (long )(sums % 32 ); }
使用上面的方法需要增加Apache Commons lang3 的依赖,这也是此方法的缺点,但是在实际使用的时候,lang3这个类一般也是要引入的,非常非常好用,提高效率的利器 (注意:这里的commons-lang3必须是 3.8版本或者更高版本,否则低于这个版本会报没有toCodePoints(CharSequence str) 和 getHostName()方法
)。
1 2 3 4 5 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8 </version> </dependency>
最终完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 import org.apache.commons.lang3.RandomUtils;import org.apache.commons.lang3.StringUtils;import org.apache.commons.lang3.SystemUtils; import java.net.Inet4Address;import java.net.UnknownHostException; public class SnowflakeIdWorker { private final long twepoch = 1489111610226L ; private final long workerIdBits = 5L ; private final long dataCenterIdBits = 5L ; private final long maxWorkerId = -1L ^ (-1L << workerIdBits); private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); private final long sequenceBits = 12L ; private final long workerIdShift = sequenceBits; private final long dataCenterIdShift = sequenceBits + workerIdBits; private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits; private final long sequenceMask = -1L ^ (-1L << sequenceBits); private long workerId; private long dataCenterId; private long sequence = 0L ; private long lastTimestamp = -1L ; private static SnowflakeIdWorker idWorker; static { idWorker = new SnowflakeIdWorker (getWorkId(),getDataCenterId()); } public SnowflakeIdWorker (long workerId, long dataCenterId) { if (workerId > maxWorkerId || workerId < 0 ) { throw new IllegalArgumentException (String.format("workerId can't be greater than %d or less than 0" , maxWorkerId)); } if (dataCenterId > maxDataCenterId || dataCenterId < 0 ) { throw new IllegalArgumentException (String.format("dataCenterId can't be greater than %d or less than 0" , maxDataCenterId)); } this .workerId = workerId; this .dataCenterId = dataCenterId; } public synchronized long nextId () { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException ( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds" , lastTimestamp - timestamp)); } if (lastTimestamp == timestamp) { sequence = (sequence + 1 ) & sequenceMask; if (sequence == 0 ) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L ; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (dataCenterId << dataCenterIdShift) | (workerId << workerIdShift) | sequence; } protected long tilNextMillis (long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } protected long timeGen () { return System.currentTimeMillis(); } private static Long getWorkId () { try { String hostAddress = Inet4Address.getLocalHost().getHostAddress(); int [] ints = StringUtils.toCodePoints(hostAddress); int sums = 0 ; for (int b : ints){ sums += b; } return (long )(sums % 32 ); } catch (UnknownHostException e) { return RandomUtils.nextLong(0 ,31 ); } } private static Long getDataCenterId () { int [] ints = StringUtils.toCodePoints(SystemUtils.getHostName()); int sums = 0 ; for (int i: ints) { sums += i; } return (long )(sums % 32 ); } public static synchronized Long generateId () { long id = idWorker.nextId(); return id; } public static void main (String[] args) { System.out.println(System.currentTimeMillis()); long startTime = System.nanoTime(); for (int i = 0 ; i < 50000 ; i++) { long id = SnowflakeIdWorker.generateId(); System.out.println(id); } System.out.println((System.nanoTime()-startTime)/1000000 +"ms" ); } }
或者使用Hutool工具后更简洁的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import cn.hutool.core.lang.Snowflake;import cn.hutool.core.util.IdUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.net.InetAddress;public class IdWorker { private static final Logger logger = LoggerFactory.getLogger(IdWorker.class); public static long nextId () { Snowflake snowflake = IdUtil.getSnowflake(1 , 1 ); return snowflake.nextId(); } public static String nextId (String prefix) { int workerId = 1 ; try { String[] ips = InetAddress.getLocalHost().getHostAddress().split("\\." ); workerId = Integer.parseInt(ips[ips.length - 1 ]); } catch (Exception e) { e.printStackTrace(); } Snowflake snowflake = IdUtil.getSnowflake(workerId % 10 , 1 ); return prefix + snowflake.nextId(); } public static void main (String[] args) { logger.info("" + nextId()); } }