package com.jd.medicine.base.common.global.id;
import com.jd.jim.cli.Cluster;
import com.jd.medicine.base.common.logging.LogUtil;
import com.jd.medicine.base.common.util.DateUtil;
import com.jd.medicine.base.common.util.IpUtil;
import com.jd.medicine.base.common.util.StringUtil;
import com.jd.ump.profiler.proxy.Profiler;
import org.slf4j.Logger;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
/**
* 功能描述:雪花算法生成全局唯一id
*
* @author yaoyizhou
* @date 2018/11/06 10:15
* @desc
*/
public class MachineIdRegist {
private static Logger logger = LogUtil.getLogger(MachineIdRegist.class);
/**
* redis实例
*/
private Cluster jimClient;
/**
* 机器码key前面一段
*/
private String machineIdRedisKey;
/**
* 机器id
*/
private static Integer machine_id;
/**
* 本地ip地址
*/
private String localIp;
/**
* 可以注册的机器个数64个
*/
private Integer MACHINE_ID_NUM = 64;
/**
* 配置项
* 初始化redis
*
* @param jimClient
*/
public void setJimClient(Cluster jimClient) {
this.jimClient = jimClient;
}
/**
* 配置项
* 初始化机器id key的前缀
*
* @param machineIdRedisKey
*/
public void setMachineIdRedisKey(String machineIdRedisKey) {
this.machineIdRedisKey = machineIdRedisKey;
}
/**
* 配置项
* 初始化时调用
* 作用:
* 1.对当前机器ip去掉“.”并转化成long类型
* 2.hash机器初始化一个机器ID
*/
public void initMachineId() {
if (StringUtil.isBlank(machineIdRedisKey)) {
logger.info("machineIdRedisKey 不能为空");
throw new RuntimeException("machineIdRedisKey is null ,please init first");
}
try {
localIp = IpUtil.getInet4Address();
} catch (Exception e) {
//如果抛异常了,直接给一个固定的ip
throw new RuntimeException("全局id机器码注册异常!");
}
Long ip_ = Long.parseLong(localIp.replaceAll("\\.", ""));
//这里取64,为后续机器Ip调整做准备。
machine_id = (int) (ip_ % MACHINE_ID_NUM);
//创建一个机器ID
createMachineId();
logger.info("初始化 machine_id :{}", machine_id);
SnowFlakeGenerator.initMachineId(machine_id);
}
/**
* 配置项
* 容器销毁前调用
* 作用:清除注册记录
*/
public void destroyMachineId() {
jimClient.del(machineIdRedisKey + machine_id);
}
/**
* 主方法:获取一个机器id
*
* @return
*/
private Integer createMachineId() {
try {
//向redis注册,并设置超时时间
Boolean aBoolean = registMachine(machine_id);
//注册成功
if (aBoolean) {
//启动一个线程更新超时时间
updateExpTimeThread();
//返回机器Id
return machine_id;
}
//检查是否被注册满了.不能注册,就直接返回,如果能注册,则直接给machine_id 赋值返回
if (!checkIfCanRegist()) {
//注册满了,加一个报警,然后抛异常
throw new RuntimeException("全局id机器码注册满,10分钟");
}
//机器码+1
incMachineId();
logger.info("createMachineId->ip:{},machineId:{}, time:{}", localIp, machine_id, DateUtil.getDate("yyyy-MM-dd HH:mm:ss"));
//递归调用
createMachineId();
} catch (Exception e) {
throw new RuntimeException("全局id机器码注册异常!");
}
return machine_id;
}
/**
* 检查是否被注册满了
*
* @return
*/
private Boolean checkIfCanRegist() {
//判断0~MACHINE_ID_NUM这个区间段的机器IP是否被占满
for (int i = 0; i <= (MACHINE_ID_NUM-1); i++) {
Boolean flag = jimClient.exists(machineIdRedisKey + i);
//如果不存在。说明还可以继续注册。直接返回i
if (!flag) {
return true;
}
}
return false;
}
/**
* 1.更新超時時間
* 注意,更新前检查是否存在机器ip占用情况
* 例如:当前ip的注册信息丢失,这时
*/
private void updateExpTimeThread() {
//开启一个线程执行定时任务:
//1.超时时间10分钟,8分钟更新一次超时时间
new Timer(localIp).schedule(new TimerTask() {
@Override
public void run() {
//检查缓存中的ip与本机ip是否一致,一致則更新時間,不一致則重新取一個机器ID
Boolean b = checkIsLocalIp(String.valueOf(machine_id));
if (b) {
logger.info("更新超时时间 ip:{},machineId:{}, time:{}", localIp, machine_id, DateUtil.getDate("yyyy-MM-dd HH:mm:ss"));
jimClient.expire(machineIdRedisKey + machine_id, 10, TimeUnit.MINUTES);
} else {
logger.info("重新生成机器ID ip:{},machineId:{}, time:{}", localIp, machine_id, DateUtil.getDate("yyyy-MM-dd HH:mm:ss"));
//重新生成机器ID,并且更改雪花中的机器ID
initMachineId();
//重新生成并注册机器id
createMachineId();
//更改雪花中的机器ID
SnowFlakeGenerator.initMachineId(machine_id);
//結束當前任務
logger.info("Timer->thread->name:{}", Thread.currentThread().getName());
this.cancel();
}
}
}, 10 * 1000, 1000 * 60 * 8);
}
/**
* 获取1~MACHINE_ID_NUM随机数
*/
private void getRandomMachineId() {
machine_id = (int) (Math.random() * MACHINE_ID_NUM);
}
/**
* 机器ID顺序获取
*/
private void incMachineId() {
logger.info("incMachineId->id-1:{}", machine_id);
if (machine_id >= MACHINE_ID_NUM) {
machine_id = 0;
} else {
machine_id += 1;
}
logger.info("incMachineId->id:{}", machine_id);
}
/**
* @param mechineId
* @return
*/
private Boolean checkIsLocalIp(String mechineId) {
String ip = jimClient.get(machineIdRedisKey + mechineId);
logger.info("checkIsLocalIp->ip:{}", ip);
return localIp.equals(ip);
}
/**
* 1.注册机器
* 2.设置超时时间
*
* @param mechineId 取值为0~MACHINE_ID_NUM
* @return
*/
private Boolean registMachine(Integer mechineId) throws Exception {
return jimClient.set(machineIdRedisKey + mechineId, localIp, 10, TimeUnit.MINUTES, false);
}
}
package com.jd.medicine.base.common.global.id;
import com.jd.medicine.base.common.logging.LogUtil;
import org.slf4j.Logger;
import java.util.Random;
/**
* 功能描述:
*
* 每一部分占用位数的默认值 雪花算法利用了64长度,我们计划利用 1+40+6+7=53
* 1占位+40位时间戳+6位机器码+7位随机数
*
* 1.可以使用(2^40 −1)/(1000∗60∗60∗24∗365)=34.8年
* 2.生成最长16位id
* 3.理论上支持每毫秒生成的id个数支持128个。
* 4.支持集群中机器个数64个。
*
* 参考:https://segmentfault.com/a/1190000011282426#articleHeader2
*
* @author yaoyizhou
* @date 2018/11/7 16:09
* @desc
*/
public class SnowFlakeGenerator {
private static Logger logger = LogUtil.getLogger(SnowFlakeGenerator.class);
/**
* 机器码占用的位数 原雪花算法默认是5,我们不需要,用6
*/
private final static int MACHINE_BIT_NUM = 6;
/**
* 数据中心占用的位数 原雪花算法默认是5,我们不需要,用0
*/
private final static int DATACENTER_BIT_NUM = 0;
/**
* 随机数的长度,采用雪花算法的默认值 12,我们用7,表示0-128
*/
private final static int SEQUENCE_BIT_NUM = 7;
/**
* 起始的时间戳
* 写代码时的时间戳
* 2018-11-07 16:06:00
*/
private final static long START_STAMP = 1541577921683L;
/**
* 用于进行末尾sequence随机数产生
*/
private final static Random RANDDOM = new Random();
private static final SnowFlakeGenerator single = new SnowFlakeGenerator();
//静态工厂方法
public static SnowFlakeGenerator getInstance() {
if (single.machineId < 0) {
throw new RuntimeException("machineId less 0,please init first");
}
return single;
}
protected static void initMachineId(Integer mechineId) {
logger.info("initMachineId->mechineId:{}", mechineId);
single.setMachineId(Integer.valueOf(mechineId));
}
/**
* datacenter编号 我们不用,默认值为0
*/
private long datacenterId = 0;
/**
* 机器编号
*/
private volatile long machineId = -1;
/**
* 当前序列号
*/
private long sequence = 0L;
/**
* 上次最新时间戳
*/
private long lastStamp = -1L;
/**
* datacenter偏移量:一次计算出,避免重复计算
*/
private int idcBitLeftOffset;
/**
* 机器id偏移量:一次计算出,避免重复计算
*/
private int machineBitLeftOffset;
/**
* 时间戳偏移量:一次计算出,避免重复计算
*/
private int timestampBitLeftOffset;
/**
* 最大序列值掩码,防止移除:一次计算出,避免重复计算
*/
private Long maxSequenceValue;
private SnowFlakeGenerator() {
this.maxSequenceValue = -1L ^ (-1L << SEQUENCE_BIT_NUM);
machineBitLeftOffset = SEQUENCE_BIT_NUM;
idcBitLeftOffset = MACHINE_BIT_NUM + SEQUENCE_BIT_NUM;
timestampBitLeftOffset = DATACENTER_BIT_NUM + MACHINE_BIT_NUM + SEQUENCE_BIT_NUM;
}
private void setMachineId(int machineId) {
if (machineId < 0 || machineId > (-1 ^ (-1 << MACHINE_BIT_NUM))) {
throw new RuntimeException("machineID less 0 or outOf maxMachineId," + machineId);
}
this.machineId = machineId;
}
/**
* 产生下一个ID
*/
public synchronized long nextId() {
long currentStamp = getTimeMill();
if (currentStamp < lastStamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
}
//相同或不同,我们都序列自增
if (currentStamp == lastStamp) {
sequence = (sequence + 1);
//如果自增序列超过了最大值,就返回到0,由于可能和之前序列重复,因此阻塞一毫秒,避免重复
if (sequence > maxSequenceValue) {
sequence = 0L;
currentStamp = tilNextMillis();
}
} else {
//时间不相同时,原雪花算法是sequence归0,但这样造成大部分尾数都一样,我们是取0-9随机数,方便以id进行路由分片
sequence = RANDDOM.nextInt(10);
}
lastStamp = currentStamp;
logger.info("return snkowId:{}<<{}|{}<<{}|{}<<{}|{}", (currentStamp - START_STAMP), timestampBitLeftOffset, datacenterId, idcBitLeftOffset, machineId, machineBitLeftOffset, sequence);
return (currentStamp - START_STAMP) << timestampBitLeftOffset | datacenterId << idcBitLeftOffset | machineId << machineBitLeftOffset | sequence;
}
private long getTimeMill() {
return System.currentTimeMillis();
}
private long tilNextMillis() {
//在一毫秒的时间内,阻塞,直到下一毫秒
long timestamp = getTimeMill();
while (timestamp <= lastStamp) {
timestamp = getTimeMill();
}
return timestamp;
}
public static void main(String args[]) {
try {
System.out.println(-1L ^ (-1L << 10));
SnowFlakeGenerator.initMachineId(50);
System.out.println("id:" + SnowFlakeGenerator.getInstance().nextId());
Thread.sleep(2000);
System.out.println("id:" + SnowFlakeGenerator.getInstance().nextId());
Thread.sleep(2000);
System.out.println("id:" + SnowFlakeGenerator.getInstance().nextId());
Thread.sleep(2000);
System.out.println("id:" + SnowFlakeGenerator.getInstance().nextId());
} catch (Exception e) {
e.printStackTrace();
}
}
}