提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
当使用rabbitmq发送一个消息时(比如钉钉通知消息),在表中需要用到一个消息id,而且我们项目部署的是集群(分布式),这个使用就需要用到雪花算法,并且去生成一个或者多个id。
一、了解雪花算法
我这边建议看一下别人的文章(https://www.cnblogs.com/h–d/p/11342741.html)
但是我们前端使用的Number类型最多只能接受53位的长度,这个时候就需要去实现特定的雪花算法。比如 把工作机器id(10位)变成一个4位的数字0,这样就是一个53位的数字了。
二、建立SnowflakeIdWorker类
@Slf4j
public class SnowflakeldWorker(
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1420041600000L;
/**序列在id中占的位数*/
private final long sequenceBits = 8L;
//机器码4bits(或者自己设定0-15)
private long machineCode = OL;
/”时间截向左移12位(12)*/
private final long timestampleftShift -12;
/*生成序列的掩码,这里为1<<12 (0b111111111111)*,我们使用的是8位,下面是8位*/
private final long sequenceMask =0b11111111L;
//上次生成ID的时间截
private long lastTimestamp = -11
private static final Pattern PATTERN HOSTNAME = Pattern.compile("A.MD+(10-9)+)$);*秒内序列(0-65535) / I
pivate static long offset = 0;
private static long next = 0;
private boolean generateByLocal = false;
//获取下一个ID(线程安全的)
public synchronized long nextld(){
long timestamp- timeGen0;
//如果当前时间小于上一次ID生成的时间截,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp){
lastTimestamp= timestamp;
throw new Runtimefxception(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp))
}
if(lastTimestamp != timestamp){
offset = 0;
lastlimestamp = timestamp;
}
/如果是同一时间生成的,则进行豪秒内序列
if (lastTimestamp=-timestamp){
offset++·
next = offset & sequenceMask;
if (next==0) {
loq.warm("maximurmid reached in I second in epoch:"4 LocalDateTime.now)+", timeStamp:"+ timestamp);
timestamp- tilNextMillis(lastTimestamp);
}
}
//移位并通过或运算拼到一起组成53位的ID
return (timestarmp-twepoch)<< tirnestampleftShift) | machineCode << sequenceBits | next;
}
protected long tilNextMillis(long lastTimestamp) {
long timestamp timeGen0;
while (timestamp <= lastTimestamp){
timestamp timeGen);
}
return timestamp;
}
//返回以豪秒为单位的当前时间
//@return 当前时间(秒)
protected long timeGen(){
return System.currentTimeMillis0;
}
public void setMachineCode(long machineCode){
//取余,保证输入不会大于15,这样的话最大就是64位,之前是53位,减去数字0(4位)加上15位就是64
this.machineCode = machineCode;
}
工具类IdUtil
private static volatile SnowflakeldWorker instance;
public static SnowflakeldWorker getInstance(){
if(instance ==null){
synchoronized(SnowflakeldWorker.class){
if(instance ==null){
instance = new SnowflakeldWorker();
}
}
}
return instance;
}
//接口描述:获取单个id,优先从实例的缓存队列中获取,实例队列默认缓存50w个id,队列为空时就会通过http请求从我们的id微服务去生成,这样就可以节省http请求节省时间和提高性能,
//适用场景:业务并发量不高(少于80并发)。且每次获取id数量较少(如少于10w个),当超过80并发并且每次获取id大于10w个,就用batchGenerateId方法
public static Long generateId(){
return IdConsumer.getId();
}
public static List<Long> batchGenerateId(int idNum){
List<Long> idList = new ArrayList();
if(idNum<= IdQueue.BATCH_GET_ID_NUM){
idList.addAll(batchGetId(idNum))
}else{
//分批获取
int c = idNum / IdQueue.BATCH_GET_ID_NUM;
int mod = idNum % IdQueue.BATCH_GET_ID_NUM;
int total = mod==0 ? c : c+1;
for(int i = 1;i<=total;i++){
int getIdNum = IdQueue.BATCH_GET_ID_NUM;
if(i==total){
getIdNum = mod;
}
idList.addAll(batchGetId(getIdNum ));
}
}
}
private static List<Long> batchGetId(int idNum){
RestTemplate restTemplate = SpringContextUtils.getBean( name:"baseRestTemplate");
String idStr = "";
try{
idStr = restTemplate.getForObject(getIdServerUrl()+"/batchGetId?idNum="+idNum,String.class);
}catch(){
//出错了再尝试一次
idStr = restTemplate.getForObject(getIdServerUrl()+"/batchGetId?idNum="+idNum,String.class);
}
return StringUtils.isNotBlank(idStr) ? JSONArray.parseArray(idStr,Long.class) : new ArrayList<>();
}
public static String getIdServerUrl(){
IdEnvConfig idEnvConfig = SpringContextUtils.getBean("idEnvConfig");
return idEnvConfig.getUrl();
}
idEnvConfig类
@Data
@Configuration(value = "idEnvConfig")
public class idEnvConfig{
@Value("${id.generator.url:http://localhost/16830}")
private String url;
}
IdConsumer类
private static ExecutorService executorService= Executors,newSingleThreadExetutor(new NamedThreadFactory("ld-Consumer",false));
public static Long getId(){
String idStr = Stringltils.EMPTY;
RestTemplate restTemplate = SpringContextUtils.gelBean("baseRestTemplate");
idstr = restTemplate.getForObject(ldUtil.getldServerUrl() + "/getld",String.class);
if(StringUtil.isNotBlank(idStr)){
id=Long.valueOf(idStr);
}else{
id = IdUtil.getInstance.nextId();
}
return id;
}
public static Long getld2(){
while(true){
Long id = IdQueue.CURRENT_QUEUE.poll();
if(id != null){
return id;
}
}else{
IdQueue.isChangeCurrent = false;
loadCurrentQueen();
}
}
//加载当前队列,把nextQueen估财给currentQueen,然后异步获取id填充nextQueen
private static synchronized void loadCurrentQueen(){
//如果没有加载中并且nextQueen不为空,则进行当前队列赋值
if (ldQueue isc hanget urrent && IdQueue.NEXT QUEUE!= null){
//将值置为true,防止在同一时刻在等待锁的线程获得锁后进来再执行ldQueue.CURRENT_QUEUE =IdQueue.NEXT _QUEUE;
IdQueue.isChangeCurrent = ture;
ldQueue,CURRENT QUEUE = IdQueue.NEXT_QUEUE
IdQueue.NEXI QUUE= null
}
//异步填充nextQueen
loadNextQueen();
}
private static void loadNextQueen(){
if( ldQueue NEXI QUEUE-= null && IdQueue.isLoadingNext){
synchronized (idQueue.LOCK){
if (ldQueue.NEXT_QUEUE== null && IdQueue.isLoadingNext){
IdQueue.isLoadingNext = true;
executorService.submit(()->{
try{
ldQueue.NEXT_QUEUE = batchGetId(IdQueue.MAX_NUM);
}finally{
//填充完,就要置为false,防止再次获取
IdQueue.isLoadingNext = false;
}
})
}
}
}
}
public static Queue batchGetId(int idNum){
Queue queue = ConcurrentLinkedQueuen();
queuc.addAll(IdUtil.batchGenerated(idNum));
return queue;
}
Queue类
public static final int MAX NUM = 500000;
//队列最小数量,当小于这个数量时触发生产
ingTypeEnum public static final int BATCH GET ID NUM = 300000;
public static volatile Queue<Long> ID_QUEUE = new ConcurrentLinkedQueue();
public static volatile Queue<Long>CURRENT_QUEUE = new ConcurrentLinkedQueue();
public static volatile Queue<Long> NEXT_QUEUE = new ConcurrentLinkedQueue();
public static volatile boolean isChangeCurrent;
public static volatile boolean isloadingNext;
public static Object LOCK = new Object();