雪花算法原理:
使用64位long型数据结构生成:1bit符号位+41bit时间戳+5bit机器id+5bit数据id+12bit自增序列;
5位数据id:左移12位
5位机器id:左移17位
42位时间戳:左移22位
由于雪花算法使用到了机器码和数据码,(如本地主机名:SKY-20200806TOR和本地主机IP地址:192.168.1.21),若是ip使用的公网ip,加上机器码,必然能保证全球唯一性;
以下是简单版的实验代码:
package com.example.dtest.snowflakeIdTest;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
/**
* ***************注意:
* 这里产生序列没有优化:
* 1、在不同时间时,就不用产生序列,在同一时间内就产生序列,若是在同一时间内,序列又溢出了(14位已经用完了),就阻塞到下一个毫秒,获取新的时间数。
* 2、若是当前时间小于上一次id的生成时间,说明系统时钟退过这个时候应当抛出异常
*
* */
public class InetAddressTest {
private long sequence = 0L;//成员变量可以报证同一个类调用时,序列时从上一次增加的。(调用一次,就会增加一次序列号)
public List<Long> getSnowFlakeId(int sumData) throws Exception{
List<Long> longList = new ArrayList<>();
// long sequence = 0L;//因为产生序列需要自增,不能在for循环里面(这里没有考虑时间问题和溢出问题详情可以参考:复杂版SnowflakeIdWorker类:)
// for(int j=0; j<=sumData;j++){
// 从来。
InetAddress ia;
ia = InetAddress.getLocalHost();// 获得本地主机的InetAddresss对象
// 输出本地主机Ip地址
System.out.println("本地主机IP地址:" + ia.getHostAddress());
// 输出本地主机名
System.out.println("本地主机名:" + ia.getHostName());
// 输出本地主机ip地址
System.out.println("本地主机Ip地址:" + ia.getHostAddress());
// 获得百度网主机的InetAdderss 对象
ia = InetAddress.getByName("www.baidu.com");// 获得主机名指定的InetAddress对象
// 输出百度网主机IP地址
System.out.println("百度网主机ip:" + ia.getHostAddress());
// 输出百度网主机名
System.out.println("百度网主机名:" + ia.getHostName());
// workId:工作机器后5bit的id
// 得到字符串的unicode码:
int[] ints = StringUtils.toCodePoints(ia.getHostAddress());
int sum = 0;
for(int i:ints){
System.out.println(i);
sum += i;
}
System.out.println("sum==========="+sum);
long a = sum % 32;
System.out.println("a========="+a);
long workId = a;
long b = a << 12L;
System.out.println("b============="+b);
// System.out.println("二进制===="+Integer.toBinaryString((int)(a<<12L)));
System.out.println("二进制==a=="+Long.toBinaryString(a));
System.out.println("二进制==b=="+Long.toBinaryString(b));
// dataId:工作机器前5bit的id
int[] ints02 = StringUtils.toCodePoints(SystemUtils.getHostName());
sum = 0;
for(int i:ints02){
System.out.println(i);
sum += i;
}
System.out.println("sum==========="+sum);
a = sum % 32;
long dataId = a;
System.out.println("a========="+a);
b = a << 17L;
System.out.println("b============="+b);
System.out.println("二进制===="+Long.toBinaryString(a));
System.out.println("二进制===="+Long.toBinaryString(b));
// 前41bit位时间搓:
/** 开始时间截 (2015-01-01) */
long twepoch = 1489111610226L;
long timestamp = System.currentTimeMillis();
long timeCha = timestamp - twepoch;
System.out.println("timeCha========="+timeCha);
System.out.println("timeCha=============二进制=="+Integer.toBinaryString((int)timeCha));
long time = timeCha << 22L;
System.out.println("time"+time);
System.out.println("time========二进制"+Long.toBinaryString(time));
// 12bit序列:依次加一
long sequenceMask = -1L ^ (-1L << 12L);
System.out.println("sequenceMask==========="+sequenceMask);
System.out.println("sequenceMask========二进制==="+Long.toBinaryString(sequenceMask));
sequence = (sequence + 1) & sequenceMask;
System.out.println("sequence======================"+sequence);
System.out.println("sequence===============二进制===="+Long.toBinaryString(sequence));
// long snawFlakeId = (592219574422732800L << 22)|(workId << 12L) |(dataId << 17) | (sequence);
long snawFlakeId = (time << 22)|(workId << 12L) |(dataId << 17) | (sequence) & (2305843009213693951L);
System.out.println("snawFlakeId==========="+snawFlakeId);
System.out.println("snawFlakeId=========二进制=="+Long.toBinaryString(snawFlakeId));
System.out.println("snawFlakeId=========二进制=="+Long.toBinaryString(2305843009213693951L));//与上这个,防止出现负数
longList.add(snawFlakeId);
// }
System.out.println("longList==========="+longList);
return longList;
}
public static void main(String[] args) throws Exception {
InetAddressTest inetAddressTest = new InetAddressTest();
for(int i =0; i < 5; i++){
inetAddressTest.getSnowFlakeId(5);
}
}
}
以下是较为完整的雪花算法:
package com.example.dtest.snowflakeIdTest;
/**
* 其实雪花算法就是把id按位打散,然后再分成上面这几块,用位来表示状态,这其实就是一种思想。
* 所以咱们实际在用的时候,也不必非得按照上面这种分割,只需保证总位数在64位即可
*
* 如果你的业务不需要69年这么长,或者需要更长时间
* 用42位存储时间戳,(1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139年
* 用41位存储时间戳,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
* 用40位存储时间戳,(1L << 40) / (1000L * 60 * 60 * 24 * 365) = 34年
* 用39位存储时间戳,(1L << 39) / (1000L * 60 * 60 * 24 * 365) = 17年
* 用38位存储时间戳,(1L << 38) / (1000L * 60 * 60 * 24 * 365) = 8年
* 用37位存储时间戳,(1L << 37) / (1000L * 60 * 60 * 24 * 365) = 4年
*
* 如果你的机器没有那么1024个这么多,或者比1024还多
* 用7位存储机器id,(1L << 7) = 128
* 用8位存储机器id,(1L << 8) = 256
* 用9位存储机器id,(1L << 9) = 512
* 用10位存储机器id,(1L << 10) = 1024
* 用11位存储机器id,(1L << 11) = 2048
* 用12位存储机器id,(1L << 12) = 4096
* 用13位存储机器id,(1L << 13) = 8192
*
* 如果你的业务,每个机器,每毫秒最多也不会4096个id要生成,或者比这个还多
* 用8位存储随机序列,(1L << 8) = 256
* 用9位存储随机序列,(1L << 9) = 512
* 用10位存储随机序列,(1L << 10) = 1024
* 用11位存储随机序列,(1L << 11) = 2048
* 用12位存储随机序列,(1L << 12) = 4096
* 用13位存储随机序列,(1L << 13) = 8192
* 用14位存储随机序列,(1L << 14) = 16384
* 用15位存储随机序列,(1L << 15) = 32768
* 注意,随机序列建议不要太大,一般业务,每毫秒要是能产生这么多id,建议在机器id上增加位
*
* 如果你的业务量很小,比如一般情况下每毫秒生成不到1个id,此时可以将随机序列设置成随机开始自增
* 比如从0到48随机开始自增,算是一种优化建议
*
* 如果你有多个业务,也可以拿出来几位来表示业务,比如用最后4位,支持16种业务的区分
*
* 如果你的业务特别复杂,可以考虑128位存储,不过这样的话,也可以考虑使用uuid了,但uuid无序,这个有序
*
* 如果你的业务很简单,甚至可以考虑32位存储,时间戳改成秒为单位…
*
* */
/**
15 * 网上的教程一般存在两个问题:
16 * 1. 机器ID(5位)和数据中心ID(5位)配置没有解决,分布式部署的时候会使用相同的配置,任然有ID重复的风险。
17 * 2. 使用的时候需要实例化对象,没有形成开箱即用的工具类。
18 *
19 * 本文针对上面两个问题进行解决,笔者的解决方案是,workId使用服务器hostName生成,
20 * dataCenterId使用IP生成,这样可以最大限度防止10位机器码重复,但是由于两个ID都不能超过32,
21 * 只能取余数,还是难免产生重复,但是实际使用中,hostName和IP的配置一般连续或相近,
22 * 只要不是刚好相隔32位,就不会有问题,况且,hostName和IP同时相隔32的情况更加是几乎不可能
23 * 的事,平时做的分布式部署,一般也不会超过10台容器。使用上面的方法可以零配置使用雪花算法,
24 * 雪花算法10位机器码的设定理论上可以有1024个节点,生产上使用docker配置一般是一次编译,
25 * 然后分布式部署到不同容器,不会有不同的配置,这里不知道其他公司是如何解决的,即使有方法
26 * 使用一套配置,然后运行时根据不同容器读取不同的配置,但是给每个容器编配ID,1024个
27 * (大部分情况下没有这么多),似乎也不太可能,此问题留待日后解决后再行补充。
28 */
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;
/**
* Twitter_Snowflake<br>
* SnowFlake的结构如下(每部分用-分开):<br>
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
* 加起来刚好64位,为一个Long型。<br>
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*/
public class SnowflakeIdWorker {
// ==============================Fields===========================================
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1489111610226L;
/** 机器id所占的位数 */
private final long workerIdBits = 5L;
/** 数据标识id所占的位数 */
private final long dataCenterIdBits = 5L;
/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
/** 支持的最大数据标识id,结果是31 */
private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);
/** 序列在id中占的位数 */
private final long sequenceBits = 12L;
/** 机器ID向左移12位 */
private final long workerIdShift = sequenceBits;
/** 数据标识id向左移17位(12+5) */
private final long dataCenterIdShift = sequenceBits + workerIdBits;
/** 时间截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
/** 工作机器ID(0~31) */
private long workerId;
/** 数据中心ID(0~31) */
private long dataCenterId;
/** 毫秒内序列(0~4095) */
private long sequence = 0L;
/** 上次生成ID的时间截 */
private long lastTimestamp = -1L;
private static SnowflakeIdWorker idWorker;
static {
idWorker = new SnowflakeIdWorker(getWorkId(),getDataCenterId());
}
//==============================Constructors=====================================
/**
* 构造函数
* @param workerId 工作ID (0~31)
* @param dataCenterId 数据中心ID (0~31)
*/
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;
}
// ==============================Methods==========================================
/**
* 获得下一个ID (该方法是线程安全的)
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
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;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift)
| (dataCenterId << dataCenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
private static Long getWorkId(){
try {
String hostAddress = Inet4Address.getLocalHost().getHostAddress();
System.out.println("hostAddress======="+hostAddress);
int[] ints = StringUtils.toCodePoints(hostAddress);
System.out.println("ints====="+ints);
int sums = 0;
for(int b : ints){
sums += b;
System.out.println("sums========"+sums);
}
System.out.println("workId=="+sums % 32);
System.out.println("二进制===="+Integer.toBinaryString((int)(sums % 32)));
return (long)(sums % 32);
} catch (UnknownHostException e) {
// 如果获取失败,则使用随机数备用
return RandomUtils.nextLong(0,31);
}
}
private static Long getDataCenterId(){
int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName());
System.out.println("ints---hostName"+ints);
int sums = 0;
for (int i: ints) {
sums += i;
System.out.println("sums======="+sums);
}
System.out.println("dataId=="+sums % 32);
System.out.println("二进制===="+Integer.toBinaryString((int)(sums % 32)));
return (long)(sums % 32);
}
/**
* 静态工具类
*
* @return
*/
public static synchronized Long generateId(){
long id = idWorker.nextId();
return id;
}
//==============================Test=============================================
/** 测试 */
public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
long startTime = System.nanoTime();
// for (int i = 0; i < 50000; i++) {
for (int i = 0; i < 10; i++) {
long id = SnowflakeIdWorker.generateId();
System.out.println(id);
System.out.println("二进制===="+Integer.toBinaryString((int)id));
}
System.out.println((System.nanoTime()-startTime)/1000000+"ms");
}
}