关于mybatis-plus的雪花算法以及主键策略ASSIGN_ID

1.有mybatis-plus 3.5.0。Sequence类还是构造雪花算法的实现类:其函数和下面这篇博客写的功能完全一致:https://www.modb.pro/db/150947
为了放置该博客失效:我还是简单介绍下:
mybitas-plus Sequence源码:

  public synchronized long nextId() {
        long timestamp = timeGen();
        //闰秒
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    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 {
                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;
    }

    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    protected long timeGen() {
    	//该SystemClock.now就是使用一个守护线程实时更新当前的时间戳
        return SystemClock.now();
    }

如果本次获取的时间戳比上次小,说明发生了时间回退,可能是全球闰秒调整造成或者你主动执行了系统时钟同步的命令。
如果时间戳差值小于5毫秒,系统会自动等待,然后继续生成。
如果是闰秒问题,按目前调整记录都是每次调整1秒,显然超过了5毫秒,那么此时系统会抛出异常。也就是如果您的业务正好发生在闰秒调整后的那一秒之内则无法完成分表的写操作。当然这种几率较小,发生后还可以在业务层面延迟重试,基本上可以解决此问题。(上边博客的讲解)

重头戏来了:在这里插入图片描述
MybatisConfiguration 类中的 GlobalConfig的id生成器,你自己不配置的话,会自动使用DefaultIdentifierGenerator作为该实现,然而默认的DefaultIdentifierGenerator 有如下4中构造器。默认会使用第一种,即InetAddress 为空作为参数传递给Sequenc
在这里插入图片描述
在这里插入图片描述其中的getDatacenterId(maxDatacenterId)会在inetAddress为null时,使用
InetAddress.getLocalHost();
在这里插入图片描述但是java这个api使用该方法:得到的却是localhost.localdomain/127.0.0.1,windows可能会取到正确的值.this.inetAddreess里面是localhost.localdomain/127.0.0.1,获得的mac也会是null,网卡的mac地址(物理地址)压根取不到
在这里插入图片描述
至于原因:建议看看这个博客 Java中InetAddress的使用(二):获取本机IP地址的正确姿势

这个就很致命,mybatis-plus默认获取的ip地址都是一定的,雪花算法(时间戳+机器号+序列号)的序列号每次都是一定的。机器号又是根据(序列号+进程id)%固定的maxWorkerId(32)。
只要集群中某两台机器的时间戳和进程号相同,那么生成的主键就一定相同,这就违背了数据库的主键唯一的原则。所以我们需要手动获得本地的ip地址,将其传递给Sequence才能保证雪花算法的唯一性
在这里插入图片描述
获得本机的ip地址

  private static InetAddress getLocalHostExactAddress() {
        try {
            InetAddress candidateAddress = null;
//            获得该主机下的所有网卡信息:包括名称,ipv4地址和ipv6地址
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (networkInterfaces.hasMoreElements()) {
                NetworkInterface iface = networkInterfaces.nextElement();
      
//          iface.isUp      Returns whether a network interface is up and running.
//iface.isLoopback Returns whether a network interface is loopback.回环地址
                //只有正在运行且地址不为回环地址的网卡才能找到本机的ip
                if (iface.isLoopback() || !iface.isUp()) {
                    continue;
                }
                // 该网卡接口下的ip会有多个,也需要一个个的遍历,找到自己所需要的
                for (Enumeration<InetAddress> inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
                    InetAddress inetAddr = inetAddrs.nextElement();
                        if (inetAddr.isSiteLocalAddress()) {
                            // 如果是site-local地址,就是它了 就是我们要找的
                            // ~~~~~~~~~~~~~绝大部分情况下都会在此处返回你的ip地址值~~~~~~~~~~~~~
                            return inetAddr;
                        }
                        // 若不是site-local地址 那就记录下该地址当作候选
                        if (candidateAddress == null) {
                            candidateAddress = inetAddr;
                        }
                }

            }

            // 如果出去loopback回环地之外无其它地址了,那就回退到原始方案吧
            return candidateAddress == null ? InetAddress.getLocalHost() : candidateAddress;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值