mybatis-plus內置雪花算法主鍵重復問題


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)}

這樣設置以后發現終於生效了,然后部署一下,問題終於解決了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM