分散式全域性id生成原始碼
阿新 • • 發佈:2018-11-10
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(); } } }