前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MybatisPlus 分布式Id

MybatisPlus 分布式Id

作者头像
全栈程序员站长
发布2022-11-16 18:10:03
6780
发布2022-11-16 18:10:03
举报

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

简单介绍下雪花算法

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

MybatisPlus 分布式Id
MybatisPlus 分布式Id

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

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

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

代码语言:javascript
复制
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();
    }
}

Jetbrains全家桶1年46,售后保障稳定

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

代码语言:javascript
复制
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的核心

代码语言:javascript
复制
/**
 * 获取下一个 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地址推演出来的两个参数

展示下代码

代码语言:javascript
复制
public Sequence() {
    this.datacenterId = getDatacenterId(maxDatacenterId);
    this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
代码语言:javascript
复制
/**
 * 数据标识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;
}
代码语言:javascript
复制
/**
 * 获取 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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/227645.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022年10月30日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档