MybatisPlus 分布式Id

MybatisPlus 分布式Id对于分布式id,有很多方案,现在大多数用的是基于雪花算法snowflake的实现,美团有leaf,百度有uid-generator,我这里记录下苞米豆在MybatisPlus3中的分布式id实现简单介绍下雪花算法雪花算法也叫雪花id,是一个64bit的整型数据,原生的snowflake是这样的:最高位不用,41bit保存时间戳,单位是毫秒,10bit的机器位,12bit的唯一序列号,可以理解是某一毫秒内,某台机器生成了不重复的序列号10bit一般一会分为5bit的datacen

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

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

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

 

简单介绍下雪花算法

 

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

MybatisPlus 分布式Id

最高位不用,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();
    }
}

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

这个接口就是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

 

 

 

 

 

 

 

 

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Lunix历史及如何学习

    Lunix历史及如何学习1.Lunix是什么1.1Lunix是操作系统还是应用程序Lunix是一套操作系统,它提供了一个完整的操作系统当中最底层的硬件控制与资源管理的完整架构,这个架构是沿袭Unix良好的传统来的,所以相当的稳定而功能强大!Lunix具有核心和系统呼叫两层。Torvalds先生在1991年写出Linux核心的时候,其实该核心仅能『驱动386所有的硬件』而已,所…

    2022年10月3日
    1
  • html5里的空心圆柱体,容积及空心圆柱体积.doc[通俗易懂]

    html5里的空心圆柱体,容积及空心圆柱体积.doc[通俗易懂]容积及空心圆柱体积高碑店中心小学段玉红教学目标:1、在巩固圆柱体积的计算公式的基础上,通过对实物的观察认识空心圆柱体(套管),知道各部分名称及之间的关系,掌握套管体积的计算公式。能正确计算套管的体积。2、在研究套管体积计算方法过程中,发现形体之间的关系,引导学生用原有知识解决新问题。培养学生知识迁移的能力。3、通过对计算体积方法的对比,体会有效提高计算正确率的最佳方法,进一步提高学生计算能力…

    2025年12月1日
    3
  • h3c s2000交换机配置命令_华三两台交换机做dhcp

    h3c s2000交换机配置命令_华三两台交换机做dhcp
    一、 组网需求:
    Switch的端口Ethernet1/0/5与DHCP服务器端相连,端口Ethernet1/0/1,Ethernet1/0/2,Ethernet1/0/3分别与DHCPClientA、DHCPClientB、DHCPClientC相连。
    (1)在Switch上开启DHCPSnooping功能。
    (2)设置Switch上端口Ethernet1/0/5为DHCPSnooping信任端口。
    (3)在Switch

    2022年10月15日
    4
  • 使用JAX-WS进行应用程序身份验证「建议收藏」

    使用JAX-WS进行应用程序身份验证「建议收藏」在JAX-WS中处理身份验证的常用方法之一是客户端提供“用户名”和“密码”,将其附加在SOAP请求标头中并发送到服务器,服务器解析SOAP文档并检索提供的“用户名”和“密码”从请求标头中进行,并从数据库中进行验证,或者使用其他任何方法。在本文中,我们向您展示如何实现上述“JAX-WS中的应用程序级别认证”。想法…在Web服务客户端站点上,只需将“用户名”和“密码”放入请…

    2022年7月15日
    22
  • oracle游标的使用详解_oracle游标失效

    oracle游标的使用详解_oracle游标失效1、游标的概念游标(CURSOR):游标是把从数据表中提取出来的数据,以临时表的形式存放在内存中,在游标中有一个数据指针,在初始状态下指向的是首记录,利用fetch语句可以移动该指针,从而对游标中的数据进行各种操作。2、游标的作用游标是用来处理使用SELECT语句从数据库中检索到的多行记录的工具。借助于游标的功能,数据库应用程序可以对一组记录逐条进行处理,每次处理一行。3、游标的类型…

    2025年7月27日
    2
  • Linux: sctp 实例

    Linux: sctp 实例https://www.opensourceforu.com/2011/12/socket-api-part-5-sctp/需要安装lksctp-tools-develyuminstalllksctp-tools-devel编译需要-lsctpgccserver.c-lsctp-oserverClient,调用connet函数时,会触发SCTP-INIT消息,消息里的IPaddress列表是根据当前机器所配置的所有IP地址来填充,如何配置这个地址列表呢?:__sctp_con

    2022年6月23日
    34

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号