MybatisPlus 分布式Id

对于分布式id,有很多方案,现在大多数用的是基于雪花算法Snowflake的实现,美团有Leaf,百度有Uidgenerator,我这里记录下苞米豆在MybatisPlus3中的分布式id实现

 

简单介绍下雪花算法

 

雪花算法也叫雪花id,是一个64bit的整型数据,原生的Snowflake是这样的:

最高位不用,41bit保存时间戳,单位是毫秒,10bit的机器位,12bit的唯一序列号,可以理解是某一毫秒内,某台机器生成了不重复的序列号

10bit 一般一会分为5bit的datacenterId存储位和5bit的workerId存储位

 

从mybatisplus3.X开始,苞米豆已经把雪花算法的java实现放在了mybatisplus中,并且提供了默认实现类

public interface IdentifierGenerator {

    /**
     * 生成Id
     *
     * @param entity 实体
     * @return id
     */
    Number nextId(Object entity);

    /**
     * 生成uuid
     *
     * @param entity 实体
     * @return uuid
     */
    default String nextUUID(Object entity) {
        return IdWorker.get32UUID();
    }
}

这个接口就是mybatisplus的id生成接口

public class DefaultIdentifierGenerator implements IdentifierGenerator {

    private final Sequence sequence;
    //无参数构造
    public DefaultIdentifierGenerator() {
        this.sequence = new Sequence();
    }
    //workerId和dataCenterId
    public DefaultIdentifierGenerator(long workerId, long dataCenterId) {
        this.sequence = new Sequence(workerId, dataCenterId);
    }

    public DefaultIdentifierGenerator(Sequence sequence) {
        this.sequence = sequence;
    }

    @Override
    public Long nextId(Object entity) {
        return sequence.nextId();
    }
}

DefaultIdentifierGenerator是默认实现类,当然我们也可以自己实现IdentifierGenerator自定义生成id,这里有两个构造参数,一个是数据中心id一个是workerId用来区分服务区域,

这两个是雪花算法里必要的。互联网模式下,这两者也会用不同的方案去做区分,比如用zk的顺序节点id,这里不展开讲,只说跟id生成相关的逻辑

Sequence 类是处理id的核心

/**
 * 获取下一个 ID
 *
 * @return 下一个 ID
 */
public synchronized long nextId() {
    //获取当前时间戳
    long timestamp = timeGen();
    //闰秒,处理时针回拨,很多时候因为时间同步产生时间点不一致,当前时间比之前的时间戳小,这个时候需要回拨时间
    if (timestamp < lastTimestamp) {
        long offset = lastTimestamp - timestamp;
        if (offset <= 5) {
            try {
                //左移一位,等待2倍时间差,消除时间回拨
                wait(offset << 1);
                //重新获取时间戳
                timestamp = 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 {
            // 时间差超过5ms,直接抛异常
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
        }
    }

    if (lastTimestamp == timestamp) {
        // 相同毫秒内,序列号自增
        // 这里有个序列面罩,按位与拿到加一后的结果
        sequence = (sequence + 1) & sequenceMask;
        if (sequence == 0) {
            // 同一毫秒的序列数已经达到最大,切换到下一秒
            timestamp = tilNextMillis(lastTimestamp);
        }
    } else {
        // 不同毫秒内,序列号置为 1 - 3 随机数
        sequence = ThreadLocalRandom.current().nextLong(1, 3);
    }

    lastTimestamp = timestamp;

    // 时间戳部分 | 数据中心部分 | 机器标识部分 | 序列号部分
    return ((timestamp - twepoch) << timestampLeftShift)
        | (datacenterId << datacenterIdShift)
        | (workerId << workerIdShift)
        | sequence;
}

这段代码里有几个点:

一是时钟回拨问题的解决,baomidou是直接等待;时钟回拨还有一些解决方案,比如从机器位拿两位做回拨计数位,我个人觉得等待就够用了;

二是同时刻序列号的自增和占满修改时间戳;

三是不同毫秒的random,我个人觉得没什么必要,直接给1其实也没问题

四是把这些拼接成64bit的结果

雪花算法的实现部分到这里就结束了,上面说的无参构造也不是真的没有数据中心id和workid,是mybatisplus根据48位MAC地址推演出来的两个参数

展示下代码

public Sequence() {
    this.datacenterId = getDatacenterId(maxDatacenterId);
    this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
 * 数据标识id部分
 */
protected static long getDatacenterId(long maxDatacenterId) {
    long id = 0L;
    try {
        InetAddress ip = InetAddress.getLocalHost();
        NetworkInterface network = NetworkInterface.getByInetAddress(ip);
        if (network == null) {
            id = 1L;
        } else {
            byte[] mac = network.getHardwareAddress();
            if (null != mac) {
                id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (maxDatacenterId + 1);
            }
        }
    } catch (Exception e) {
        logger.warn(" getDatacenterId: " + e.getMessage());
    }
    return id;
}
/**
 * 获取 maxWorkerId
 */
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
    StringBuilder mpid = new StringBuilder();
    mpid.append(datacenterId);
    String name = ManagementFactory.getRuntimeMXBean().getName();
    if (StringUtils.isNotBlank(name)) {
        /*
         * GET jvmPid
         */
        mpid.append(name.split(StringPool.AT)[0]);
    }
    /*
     * MAC + PID 的 hashcode 获取16个低位
     */
    return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}

这里看着唬人,大多是一些补码运算,移位运算,逻辑运算,用手算一算就清楚了,把大学课堂再拿出来捋一捋

Time

 

 

 

 

 

 

 

 

你可能感兴趣的:(很粗系列,分布式)