分布式ID生成器

1.创建IDCreater

package com.etone.api.pos.component;


import com.etone.api.pos.config.SystemProperties;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Description:全局唯一ID生成 使用方式:静态工具类(千万不能spring bean方式注入使用)
 * @Author xuchun
 * @Date 2018/4/17 19:00
 */
@Component
public class IDCreater {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SystemProperties sysProperties;

    private static Sequence xnsn = null; //虚拟sn号



    @PostConstruct
    public void init(){
        try {
            if(sysProperties==null
                    || StringUtils.isEmpty(sysProperties.getDataCenterId())
                    || StringUtils.isEmpty(sysProperties.getWorkerId())){
                throw new RuntimeException("----------------初始化业务序列号生成器获取配置文件失败");
            }
            Integer workerId=Integer.valueOf(sysProperties.getWorkerId());//物理节点
            Integer dataCenterId=Integer.valueOf(sysProperties.getDataCenterId());//数据中心
            logger.info("workerId:" + workerId + "\n" + "dataCenterId:" + dataCenterId);
            xnsn = new Sequence(workerId,dataCenterId);
            logger.info("-------xnsn:【"+xnsn+"】-------");
        } catch (RuntimeException e) {
            logger.error("error:"+e);
            throw new RuntimeException(e);
        }
    }
    interface Productor{
        //获取下一个值
        String nextval();
    }

    public enum IdType implements Productor{


        虚拟SN号("01");   //虚拟SN号


        private String type;


        // 构造方法
        IdType(String type) {
            this.type = type;
        }


        @Override
        public String nextval() {
            if(this.type.equals("01")){
                return "01"+xnsn.nextId().toString();
            }else{
                throw new RuntimeException("未知的主键类型,无法生成序列号type:"+this.type);
            }
        }
    }
}

2.创建sequence

package com.etone.api.pos.component;


import com.baomidou.mybatisplus.toolkit.SystemClock;

/**
 * 基于Twitter的Snowflake算法实现分布式高效有序ID生产黑科技(sequence)
 * 
 * <br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * <br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * <br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * <br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * <br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * <br>
 * <br>
 * 加起来刚好64位,为一个Long型。<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 * 
 * @author lry
 */
public class Sequence {

	/** 起始时间戳,用于用当前时间戳减去这个时间戳,算出偏移量 **/
    private final long startTime = 1519740777809L;

    /** workerId占用的位数5(表示只允许workId的范围为:0-1023)**/
    private final long workerIdBits = 5L;
    /** dataCenterId占用的位数:5 **/
    private final long dataCenterIdBits = 5L;
    /** 序列号占用的位数:12(表示只允许workId的范围为:0-4095)**/
    private final long sequenceBits = 12L;

    /** workerId可以使用的最大数值:31 **/
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /** dataCenterId可以使用的最大数值:31 **/
    private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits);

    private final long workerIdShift = sequenceBits;
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    private final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;

    /** 用mask防止溢出:位与运算保证计算的结果范围始终是 0-4095 **/
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long workerId;
    private long dataCenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    private boolean isClock = false;

    /**
     * 基于Snowflake创建分布式ID生成器
     * <p>
     * 注:sequence
     *
     * @param workerId     工作机器ID,数据范围为0~31
     * @param dataCenterId 数据中心ID,数据范围为0~31
     */
    public Sequence(long workerId, long dataCenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenter Id can't be greater than %d or less than 0", maxDataCenterId));
        }

        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

	public void setClock(boolean clock) {
        isClock = clock;
    }

    /**
     * 获取ID
     *
     * @return
     */
    public synchronized Long nextId() {
        long timestamp = this.timeGen();

        // 闰秒:如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    this.wait(offset << 1);
                    timestamp = this.timeGen();
                    if (timestamp < lastTimestamp) {
                        throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            } else {
                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
            }
        }

        // 解决跨毫秒生成ID序列号始终为偶数的缺陷:如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            // 通过位与运算保证计算的结果范围始终是 0-4095
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = this.tilNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变,毫秒内序列重置
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        /*
         * 1.左移运算是为了将数值移动到对应的段(41、5、5,12那段因为本来就在最右,因此不用左移)
         * 2.然后对每个左移后的值(la、lb、lc、sequence)做位或运算,是为了把各个短的数据合并起来,合并成一个二进制数
         * 3.最后转换成10进制,就是最终生成的id
         */
        return ((timestamp - startTime) << timestampLeftShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    /**
     * 保证返回的毫秒数在参数之后(阻塞到下一个毫秒,直到获得新的时间戳)
     *
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }

        return timestamp;
    }

    /**
     * 获得系统当前毫秒数
     *
     * @return timestamp
     */
    private long timeGen() {
        if (isClock) {
            // 解决高并发下获取时间戳的性能问题
            return SystemClock.now();
        } else {
            return System.currentTimeMillis();
        }
    }

}

3.

ackage apipos;

import com.etone.ApiPosRun;
import com.etone.api.pos.component.IDCreater;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes= ApiPosRun.class)
@RunWith(SpringRunner.class)
@EnableAutoConfiguration
public class IdCreaterTest {
    @Test
    public void test(){
        System.out.println("虚拟终端号:" + IDCreater.IdType.虚拟SN号.nextval());
    }

}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值