• mybatisplus内置雪花算法主键重复问题


    Mybatis-Plus 使用ID_WORKER生成主键id重复

    问题描述

    目前项目使用的id是mybatis-plus 内置的主键生成策略 ID_WORKER ,最近测试在做性能压测,部署架构是单服务集群的部署方式,然后就发现了id重复的异常,异常如下

    注意 sae serverless集群部署集群也会出现主键id重复 虽然你是不同节点部署

    问题分析

    首先分析的是id生成是不是就是重复了,先关掉其中一台机器,单机跑,这个时候发现压到1000的并发都没有出现过id重复,这个说明单机情况下不存在id重复问题,说明只有集群的情况下才会出现。

    再分析一下id生成的几个要素,雪花算法的核心能影响到id生成的几个因素:1.服务器时间2.workId(机器 ID 部分)3.datacenterId(数据标识 ID 部分)。先检查了一下服务器时间,都是一样的,然后再看一下workId的生成,我们先看一下源码。

    public Sequence() {
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }
    //获取workerId
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
            StringBuilder mpid = new StringBuilder();
            mpid.append(datacenterId);
            //代表正在运行的Java虚拟机的名称。
            String name = ManagementFactory.getRuntimeMXBean().getName();
            if (StringUtils.isNotEmpty(name)) {
                /*
                 * GET jvmPid
                 */
                mpid.append(name.split(StringPool.AT)[0]);
            }
            /*
             * MAC + PID 的 hashcode 获取16个低位
             */
            return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
        }

    这里生成的workerId主要核心影响就是获取运行的虚拟机名称,现在猜测就是有可能是由于两台机的虚拟机名称可能一样,导致拿到的workerId一样。这个没有具体去验证,猜测是这样的。那我们尝试修改一下这个workerId的值。

    问题解决

    1.先设置workerId为随机数,这样保证每个机器部署的时候拿到的都是随机数

    # 设置随机
    mybatis-plus.global-config.worker-id: ${random.int}

    这里你启动项目的时候回发现一个异常

    异常很明显,这里的worer id 必选小于31,那我们就需要修改一下随机数

    # 设置随机
    mybatis-plus.global-config.worker-id: ${random.int(1,31)}

    这个时候我们先看一下我们设置参数有没有生效,为了比较明显看到效果,我们直接设置worker-id为一个固定值20,再断点看一下,我们找到com.baomidou.mybatisplus.core.toolkit.IdWorker这个核心类,获取id的核心方法是com.baomidou.mybatisplus.core.toolkit.IdWorker#getId,那我们就在这里加一个断点看下

    public class IdWorker {
    
        /**
         * 主机和进程的机器码
         */
        private static Sequence WORKER = new Sequence();
        //获取id
        public static long getId() {
            return WORKER.nextId();
        }
    
        public static String getIdStr() {
            return String.valueOf(WORKER.nextId());
        }
    
        /**
         * <p>
         * 有参构造器
         * </p>
         *
         * @param workerId     工作机器 ID
         * @param datacenterId 序列号
         */
        public static void initSequence(long workerId, long datacenterId) {
            WORKER = new Sequence(workerId, datacenterId);
        }
    
        /**
         * <p>
         * 使用ThreadLocalRandom获取UUID获取更优的效果 去掉"-"
         * </p>
         */
        public static String get32UUID() {
            ThreadLocalRandom random = ThreadLocalRandom.current();
            return new UUID(random.nextLong(), random.nextLong()).toString().replace(StringPool.DASH, StringPool.EMPTY);
        }
    
    }

    断点后的结果是:

    这个时候看到workerId没有生效,我们继续分析下源码。

    项目启动的时候,mybatis-plus 会调用一个com.baomidou.mybatisplus.core.MybatisConfiguration#init方法来初始化配置信息,我们看下代码

    public void init(GlobalConfig globalConfig) {
            // 初始化 Sequence
        //这里需要同时设置workerId和datacenterId
            if (null != globalConfig.getWorkerId()
                && null != globalConfig.getDatacenterId()) {
                IdWorker.initSequence(globalConfig.getWorkerId(), globalConfig.getDatacenterId());
            }
            // 打印 Banner
            if (globalConfig.isBanner()) {
                System.out.println(" _ _   |_  _ _|_. ___ _ |    _ ");
                System.out.println("| | |\\/|_)(_| | |_\\  |_)||_|_\\ ");
                System.out.println("     /               |         ");
                System.out.println("                        "+MybatisPlusVersion.getVersion()+" ");
            }
        }

    我们发现workerId和datacenterId必须同时设置才会获取我们设置的值。

    那我们就修改配置一下

    # 设置随机
    mybatis-plus.global-config.worker-id: ${random.int(1,31)}
    mybatis-plus.global-config.datacenter-id: ${random.int(1,31)}

    yml版本

    mybatis-plus:
      global-config:
        datacenter-id: ${random.int(1,31)}
        worker-id: ${random.int(1,31)}

    这样设置以后发现终于生效了,然后部署一下,问题终于解决了

  • 相关阅读:
    mysql 时间戳 转 时间
    VSCode搭建VUE 开发环境
    虚拟通信
    JavaScript 获取客户端计算机硬件及系统信息
    Thinkphp关联模型BELONGS_TO
    docker部署rancher踩坑篇
    青龙面板 脚本 依赖库下载安装
    Linux 随记
    Tekton DAG代码
    手写Spring valar
  • 原文地址:https://www.cnblogs.com/shanheyongmu/p/15904285.html
Copyright © 2020-2023  润新知