springBoot整合自定义的雪花算法
1 配置pom文件
# 雪花算法配置数据中心和机器编号,不同机器组合不能重复
snowflake:
datacenterId: 1
machineId: 2
2 编写配置文件
SnowFlakeFactory.java
package com.un.framework.snowflack;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* 雪花算法,解决时间回拨问题
* 整合业务,需要根据业务编号生成id
*
* @author shiye
* @date 2020-05-27 15:41
*/
public class SnowFlakeFactory {
/**
* 起始的时间戳
* 2020-01-01 00:00:00 毫秒
*/
private final static long START_STMP = 1577808000000L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数
/**
* 每一部分的最大值
* MAX_DATACENTER_NUM = 31
* MAX_MACHINE_NUM = 31
* MAX_SEQUENCE = 4095
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId = 0; //数据中心
private long machineId = 0; //机器标识
private long sequence = 0L; //序列号
private long lastStmp = -1L;//上一次时间戳
/**
* 最大容忍时间, 单位毫秒, 即如果时钟只是回拨了该变量指定的时间, 那么等待相应的时间即可;
* 考虑到sequence服务的高性能, 这个值不易过大
*/
private static final long MAX_BACKWARD_MS = 5;
//最大扩展字段
private long maxExtension = 2L;
/**
* 保留machineId和lastTimestamp, 以及备用machineId和其对应的lastTimestamp
*/
private static Map<Long, Long> machineIdLastTimeMap = new ConcurrentHashMap<>();
/**
* 初始化数据中心位,和机器标识
* 0 < datacenterId < MAX_DATACENTER_NUM 31
* 0 < machineId < MAX_MACHINE_NUM 31
*
* @param datacenterId
* @param machineId
*/
public SnowFlakeFactory(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException(" datacenterId 必须介于[0,31] ");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException(" machineId 必须介于[0,31] ");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
//初始化时间 machineIdLastTimeMap
machineIdLastTimeMap.put(machineId, getNewstmp());
}
/**
* 产生下一个ID
*
* @return
*/
public synchronized String nextId(BRStyle brStyle) {
//现存的扩展字段
long extension = 0L;
//获取当前时间毫秒数
long currStmp = getNewstmp();
//lastStmp = currStmp + 100;
if (currStmp < lastStmp) {
//throw new RuntimeException("时钟向后移动,拒绝生成id");
// 如果时钟回拨在可接受范围内, 等待即可
long offset = lastStmp - currStmp;
//如果回拨时间不超过5毫秒,就等待相应的时间
if (offset <= MAX_BACKWARD_MS) {
try {
//睡(lastTimestamp - currentTimestamp)ms让其追上
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(offset));
currStmp = getNewstmp();
//如果时间还小于当前时间,那么利用扩展字段加1
//或者是采用抛异常并上报
if (currStmp < lastStmp) {
//扩展字段
extension += 1;
if (extension > maxExtension) {
//服务器时钟被调整了,ID生成器停止服务.
throw new RuntimeException(String.format("时钟向后移动。拒绝生成的id %d 毫秒", lastStmp - currStmp));
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
//扩展字段
extension += 1;
if (extension > maxExtension) {
//服务器时钟被调整了,ID生成器停止服务.
throw new RuntimeException(String.format("时钟向后移动,超出扩展位,拒绝生成的id %d 毫秒", lastStmp - currStmp));
}
//获取可以用的workid,对应的时间戳,必须大于当前时间戳
tryGenerateKeyOnBackup(currStmp);
}
}
if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastStmp = currStmp;
long id = (currStmp - START_STMP) << (TIMESTMP_LEFT - extension) //时间戳部分
| datacenterId << DATACENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
//如果时间戳回拨就让时间少移动一位
return brStyle.getCode() + id;
}
/**
* 自旋锁获取当前时间戳
*
* @return
*/
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
/**
* 获取当前时间毫秒数
*
* @return
*/
private long getNewstmp() {
return System.currentTimeMillis();
//测试时间回拨
//return 53501026489350000l;
}
/**
* 尝试在machineId的备份machineId上生成
* 核心优化代码在方法tryGenerateKeyOnBackup()中,BACKUP_COUNT即备份machineId数越多,
* sequence服务避免时钟回拨影响的能力越强,但是可部署的sequence服务越少,
* 设置BACKUP_COUNT为3,最多可以部署1024/(3+1)即256个sequence服务,完全够用,
* 抗时钟回拨影响的能力也得到非常大的保障。
*
* @param currentMillis 当前时间
*/
private long tryGenerateKeyOnBackup(long currentMillis) {
// 遍历所有machineId(包括备用machineId, 查看哪些machineId可用)
for (Map.Entry<Long, Long> entry : machineIdLastTimeMap.entrySet()) {
this.machineId = entry.getKey();
// 取得备用machineId的lastTime
Long tempLastTime = entry.getValue();
lastStmp = tempLastTime == null ? 0L : tempLastTime;
// 如果找到了合适的machineId,返回合适的时间,
if (lastStmp <= currentMillis) {
return lastStmp;
}
}
// 如果所有machineId以及备用machineId都处于时钟回拨, 那么抛出异常
throw new IllegalStateException("时钟在向后移动,当前时间是 " + currentMillis + " 毫秒,machineId映射 = " + machineIdLastTimeMap);
}
}
定义一个枚举,主要是为了为特定业务生成特定的id
package com.un.framework.snowflack;
/**
* @author shiye
* @date 2020-05-27 15:46
*/
public enum BRStyle {
CO("CO", "社区模块"),
;
private final String code;
private final String info;
BRStyle(String code, String info) {
this.code = code;
this.info = info;
}
public String getCode() {
return code;
}
public String getInfo() {
return info;
}
}
容器初始化的时候加载到内存中
package com.un.framework.snowflack;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author shiye
* @date 2020-05-27 15:54
*/
@Configuration
public class IDGenderConfig {
//数据中心[0,31]
@Value("${snowflake.datacenterId}")
private long datacenterId;
//机器标识[0,31]
@Value("${snowflake.machineId}")
private long machineId;
@Bean
public SnowFlakeFactory getSnowFlakeFactory() {
SnowFlakeFactory snowFlakeFactory = new SnowFlakeFactory(datacenterId,machineId);
return snowFlakeFactory;
}
}
3 测试
package com.un.project.tool.snowflake;
import com.un.framework.snowflack.BRStyle;
import com.un.framework.snowflack.SnowFlakeFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @author shiye
* @date 2020-05-27 16:04
*/
@RestController
@RequestMapping("/snowflake")
public class SnowFlakeController {
@Autowired
private SnowFlakeFactory snowFlakeFactory;
/**
* 雪花算法测试
*
* @return
*/
@GetMapping("/getCOID")
public String genSnowFlake() {
return snowFlakeFactory.nextId(BRStyle.CO);
}
/**
* 批量生成id
* 开始时间2020-05-27T17:01:34.176
* 结束时间2020-05-27T17:01:34.463
* 200000
* 结论:300ms生成 20w个无重复的id
* @return
*/
@GetMapping("/batchCreateID")
public Set batchCreateID() throws ExecutionException, InterruptedException {
int count = 200000;
System.out.println("开始生成id......");
ExecutorService executor = Executors.newCachedThreadPool();
List countList = new ArrayList();
//测试生成20w个id
for (int i = 0; i < 200000; i++) {
countList.add(i);
}
//使用set测试是否有重复,结果没有任何重复
Set list = Collections.synchronizedSet(new HashSet<>());
System.out.println("开始时间" + LocalDateTime.now());
countList.parallelStream().forEach((i) -> {
Future<String> futureTask = executor.submit(() -> {
return snowFlakeFactory.nextId(BRStyle.CO);
});
String id = null;
try {
id = futureTask.get();
} catch (Exception e2) {
e2.printStackTrace();
}
list.add(id);
});
System.out.println("结束时间" + LocalDateTime.now());
System.out.println(list.size());
return list;
}
}
以上是 springBoot整合自定义的雪花算法 的全部内容, 来源链接: utcz.com/z/516840.html