需求:我们需要给请求一个唯一标识,用来标识一个请求和响应的关联关系,我们要求请求的id必须唯 一,且不能占用过大的空间
分析:1、uuid,但uuid作为唯一标识占用空间太大(16字节) 2、使用雪花算法(8字节)
构思:
代码实现:
package com.example.utils;
import java.util.concurrent.atomic.LongAdder;
/**
* @Description: 手写雪花算法 生成请求id
* @ClassName: IdGenerator
* @Author: dianZiMuYu
* @Date: 2023/7/31 15:43
*/
public class IdGenerator {
public static final long START_STAMP = DateUtil.get("2021-11-26").getTime();
public static final long DATA_CENTER_BIT = 5L;
public static final long MACHINE_BIT = 5L;
public static final long SEQUENCE_BIT = 12L;
/**
* 移位操作 提高速度
*
*/
public static final long DATA_CENTER_MAX = ~(-1L << DATA_CENTER_BIT);
public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);
/**
* 移位 + 或操作
* 时间戳 (42) 机房号 (5) 机器号 (5) 序列号 (12)
*/
public static final long TIMESTAMP_LEFT_OFFSET = DATA_CENTER_BIT + MACHINE_BIT + SEQUENCE_BIT;
public static final long DATA_CENTER_LEFT_OFFSET = MACHINE_BIT + SEQUENCE_BIT;
public static final long MACHINE_LEFT_OFFSET = SEQUENCE_BIT;
/**
* 属性
*/
private long dataCenterId;
private long machineId;
/**
* JDK1.8新增的一个原子性操作类
*/
private LongAdder sequenceId = new LongAdder();
private long lastTimeStamp = -1L;
public IdGenerator(long dataCenterId, long machineId) {
// 判断传入的参数是否合法
if(dataCenterId > DATA_CENTER_MAX || machineId > MACHINE_MAX){
throw new IllegalArgumentException("你传入的数据中心编号或机器号不合法.");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}
public long getId(){
long currentTime = System.currentTimeMillis();
long timeStamp = currentTime - START_STAMP;
// 判断时钟回拨
if(timeStamp < lastTimeStamp){
throw new RuntimeException("您的服务器进行了时钟回调.");
}
if (timeStamp == lastTimeStamp){
sequenceId.increment();
//同一时间获得的数字数量大于 序列号最大值 就等待下一时刻
if(sequenceId.sum() >= SEQUENCE_MAX){
timeStamp = getNextTimeStamp();
sequenceId.reset();
}
} else {
sequenceId.reset();
}
lastTimeStamp = timeStamp;
/**
* 同一时刻 每 获得一个数 序列号就加一
*/
long sequence = sequenceId.sum();
return timeStamp << TIMESTAMP_LEFT_OFFSET | dataCenterId << DATA_CENTER_LEFT_OFFSET
| machineId << MACHINE_LEFT_OFFSET | sequence ;
}
private long getNextTimeStamp() {
// 获取当前的时间戳
long current = System.currentTimeMillis() - START_STAMP;
// 如果一样就一直循环,直到下一个时间戳
while (current == lastTimeStamp){
current = System.currentTimeMillis() - START_STAMP;
}
return current;
}
}
遗留问题:解决时钟回拨 后续处理