雪花算法
我们都知道在一个分布式系统中生成一个无重复的标识是非常重要的,业界也有很多算法。
雪花(snowflake)在自然界中,是极具独特美丽,又变幻莫测的东西:
- 雪花属于六方晶系,它具有四个结晶轴,其中三个辅轴在一个基面上,互相以60度的角度相交,第四轴(主晶轴)与三个辅轴所形成的基面垂直;
- 雪花的基本形状是六角形,但是大自然中却几乎找不出两朵完全相同的雪花,每一个雪花都拥有自己的独有图案,就象地球上找不出两个完全相同的人一样。许多学者用显微镜观测过成千上万朵雪花,这些研究最后表明,形状、大小完全一样和各部分完全对称的雪花,在自然界中是无法形成的。
雪花算法:
雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。
自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。
算法描述:
- 最高位是符号位,始终为0,不可用。
- 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
- 10位的机器标识,10位的长度最多支持部署1024个节点。
12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
package com.cnblogs.util;
/**
- Twitter_Snowflake
- SnowFlake的结构如下(每部分用-分开):
- 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
- 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
- 41位时间戳(毫秒级),注意,41位时间戳不是存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 - 开始时间戳)
- 得到的值),这里的的开始时间戳,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间戳,可以使用69年,年T = (1L << 41) / (1000L 60 60 24 365) = 69
- 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
- 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间戳)产生4096个ID序号
- 加起来刚好64位,为一个Long型。
SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*/
public class SnowflakeIdWorker {// ==============================Fields===========================================
/* 开始时间截 (201-01-01) /
private final long twepoch = 1514736000000L;/* 机器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;//==============================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("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id 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();
}//==============================Test=============================================
/* 测试 /
public static void main(String[] args) {
SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
for (int i = 0; i < 10; i++) {long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
}
}
}
- Twitter_Snowflake
注意:在分布式系统中给每台机器设置一个int64的机器码,可以是IP编号+随机数,如192168011234
(192.168.0.1
+1234)
结论:
理论上生成速率为kw/秒,所以完全满足一般企业级应用, 算法可靠(去重处理在此也是多此一举);
性能:100W+/秒;
还没有评论,来说两句吧...