关于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
关于mybatis-plus的雪花算法以及主键策略ASSIGN_ID_第1张图片
关于mybatis-plus的雪花算法以及主键策略ASSIGN_ID_第2张图片其中的getDatacenterId(maxDatacenterId)会在inetAddress为null时,使用
InetAddress.getLocalHost();
关于mybatis-plus的雪花算法以及主键策略ASSIGN_ID_第3张图片但是java这个api使用该方法:得到的却是localhost.localdomain/127.0.0.1,windows可能会取到正确的值.this.inetAddreess里面是localhost.localdomain/127.0.0.1,获得的mac也会是null,网卡的mac地址(物理地址)压根取不到
关于mybatis-plus的雪花算法以及主键策略ASSIGN_ID_第4张图片
至于原因:建议看看这个博客 Java中InetAddress的使用(二):获取本机IP地址的正确姿势

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

  private static InetAddress getLocalHostExactAddress() {
        try {
            InetAddress candidateAddress = null;
//            获得该主机下的所有网卡信息:包括名称,ipv4地址和ipv6地址
            Enumeration 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 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;
    }

你可能感兴趣的:(java,开发语言)