分布式全局id生成源代码

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();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/u013465194/article/details/83901956