目录
snowflake算法简介
在大型分布式系统中,迫切需要唯一id,唯一id的生成方式有很多种,可以使用uuid,可以使用redis的自增序列,可以使用数据库的自增ID,每种方式各有优缺点,今天给大家介绍一下如何使用snowflake算法生成唯一id。
long类型占64位,我们把64位划分为几个模块,第1位为符号位,接下来的41位存放linux时间戳,中间10位存放机房序号和机器序号,最后12位存放自增序列。
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
符号位 - linux时间戳 - 机房序号 - 机器序号 - 自增序列
位数说明
1.符号位(1位)
第一位是符号位,符号位为0,表示正数。
2.linux时间戳(41位)
linux时间戳占最高位的41位,是时间戳的差值,比如我们定义2015年1月1日为参照时间,那么是当前时间减去参照时间之后的值存入这41位中。最多可以支持 (Math.pow(2,41)-1)/(1000l*60*60*24*365)=69年。
3.机房序号(5位)
Math.pow(2,5)-1=31,所以最多可以有31个机房。
4.机器序号(5位)
Math.pow(2,5)-1=31,所以每个机房最多可以有31台机器。
5.自增序列(12位)
Math.pow(2,12)-1=4095,所以每个机房中的每台机器同一毫秒最多生成4095个序列,也就是4095个id。
如果负载均衡做得好,机器也充足的话,一毫秒最多可以生成 31*31*4095=3935295个无重复的id。
sonwflake算法Java实现
/**
* @ClassName Snowflake
* @Description 使用snowflake算法生成id
* @Author boy
* @Date 2019/6/11 11:30 AM
*/
public class Snowflake {
//机群所占位数
private final static int fleetBit = 5;
//机器码所占位数
private final static int machineBit = 5;
//自增序列所占位数
private final static int sequenceBit = 12;
//机器码位移量
private final static int machineDisplacement = sequenceBit;
//机群位移量
private final static int fleetDisplacement = machineBit + sequenceBit;
//时间戳位移量
private final static int timeDisplacement = fleetBit + machineBit + sequenceBit;
//参照时间(2018-01-01 00:00:00)
private final static long referenceTime = 1514736000000l;
//自增序列最大值
private final static int sequenceMax = -1^(-1<<12);
//自增序列
private static int sequence = 0;
//上一次生成自增序列的时间
private static long lastTimeStamp = -1l;
/*
* @Author boy
* @Description 获取全局唯一id
* @Date 2019/6/11 5:27 PM
* @Param [fleetCode, machineCode]
* @return long
*/
public static synchronized long getNextId(int fleetCode,int machineCode){
long currentTimeStamp = getCurrentTimeStamp();
if (lastTimeStamp==currentTimeStamp){
if (sequence>=sequenceMax){
currentTimeStamp = getNextTimeStamp();
lastTimeStamp = currentTimeStamp;
sequence = 0;
} else {
sequence++;
}
} else if (lastTimeStamp<currentTimeStamp){
sequence = 0;
lastTimeStamp = currentTimeStamp;
} else if (lastTimeStamp>currentTimeStamp){
}
long id = (currentTimeStamp-referenceTime)<<timeDisplacement |
fleetCode<<fleetDisplacement |
machineCode<<machineDisplacement |
sequence;
return id;
}
/*
* @Author boy
* @Description 获取当前时间戳,精确到毫秒
* @Date 2019/6/11 5:26 PM
* @Param []
* @return long
*/
private static long getCurrentTimeStamp(){
return System.currentTimeMillis();
}
/*
* @Author boy
* @Description 当前毫秒自增序列已经达到最大值,等待获取下一毫秒
* @Date 2019/6/11 5:27 PM
* @Param []
* @return long
*/
private static long getNextTimeStamp(){
long timeStamp = getCurrentTimeStamp();
while (timeStamp<=lastTimeStamp){
timeStamp = getCurrentTimeStamp();
}
return timeStamp;
}
}
性能与正确性验证
1.单线程测试snowflake算法的性能
/**
* @ClassName SnowflakeTest
* @Description 单线程测试snowflake算法的性能
* @Author boy
* @Date 2019/6/11 7:40 PM
*/
public class SnowflakeTest {
public static void main(String args[]){
long startTime = System.currentTimeMillis();
System.out.println("startTime---------------------------------------" + startTime);
long num = 100000000l;
for(int i=0;i<num;i++){
Snowflake.getNextId(1,2);
}
long endTime = System.currentTimeMillis();
System.out.println("endTime---------------------------------------" + endTime);
long timeQuantum = startTime - endTime;
System.out.println("(startTime-endTime)---------------------------------------" + timeQuantum);
System.out.println("(num/(startTime-endTime))---------------------------------------" + num/timeQuantum);
}
}
执行结果:
startTime---------------------------------------1560241596412
endTime---------------------------------------1560241620874
(startTime-endTime)----------------------------------------24462
(num/(startTime-endTime))----------------------------------------4087
理论上单台机器每毫秒最多可以生成4095个id。
实际执行结果:生成1亿个ID,单台机器使用了24462毫秒,平均每毫秒生成了4087个id。
2.多线程测试生成的id是否有重复
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @ClassName SnawflakeThreadTest
* @Description 多线程测试snowflake算法
* @Author boy
* @Date 2019/5/29 8:56 PM
*/
public class SnowflakeThreadTest {
public static void main(String[] args){
long num = 1000;
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i=0;i<num;i++) {
executorService.execute(new SnowflakeThread());
}
executorService.shutdown();
}
}
class SnowflakeThread implements Runnable{
public void run() {
System.out.println(Snowflake.getNextId(1,2));
}
}
执行结果:
190915047550230528
190915047550230529
190915047550230530
190915047554424832
190915047554424833
190915047554424834
190915047558619136
190915047558619137
190915047558619138
190915047562813440
190915047562813441
190915047562813442
-----------------
3.LRU算法验证id是否有重复
验证思路:生成1亿个id,每次生成的id和最近的1万个id进行比较,看是否有相同的。
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @ClassName SnowflakeDoubleTest
* @Description 使用LRU算法验证snowflake算法生成的id是有有重复
* 生成1亿个id,LRU的深度为1万。每次查询最近的1万个id和新生成的是否会用重复
* @Author boy
* @Date 2019/6/13 12:32 PM
*/
public class SnowflakeDoubleTest {
//队列的深度
private static int deep = 10000;
//需要生成的id数量
private static long num = 100000000l;
//重复id的数量
private static int repeatNum = 0;
public static void main(String args[]){
LRU lru = new LRU(deep);
for(int i=0;i<num;i++){
long id = Snowflake.getNextId(1,2);
if(lru.get(id)!=null){
System.out.println(id);
repeatNum++;
}else {
lru.put(id,id);
}
}
System.out.println("重复的id数为:" + repeatNum);
}
}
/**
* @ClassName LRU
* @Description 基于LinkedHashMap的LRU算法
* @Author boy
* @Date 2019/6/13 10:27 AM
*/
class LRU extends LinkedHashMap {
//缓存长度
int length;
public LRU(int length){
super(length,0.75f,true);
this.length = length;
}
/*
* @Author boy
* @Description LRU算法的关键方法,超过最大长度就移除尾部元素
* @Date 2019/6/13 12:24 PM
* @Param [map]
* @return boolean
*/
@Override
public boolean removeEldestEntry(Map.Entry map){
return size()>length;
}
}
执行结果:
重复的id数为:0