mybatis-plus 使用雪花算法ID生成策略借鑒


一、使用

  Mybatis-plus提供了@IdType注解為實體類配置主鍵生成策略,如源碼中所示,共有5種策略。

public enum IdType {
    /**
     * 數據庫ID自增
     * <p>該類型請確保數據庫設置了 ID自增 否則無效</p>
     */
    AUTO(0),
    /**
     * 無狀態,該類型為未設置主鍵類型(注解里等於跟隨全局,全局里約等於 INPUT)
     */
    NONE(1),
    /**
     * 用戶輸入ID(insert前自行set主鍵值)
     * <p>該類型可以通過自己注冊自動填充插件進行填充</p>
     */
    INPUT(2),

    /* 以下3種類型、只有當插入對象ID 為空,才自動填充。 */
    /**
     * 分配ID (主鍵類型為number或string),
     * 默認實現類 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主鍵類型為 string)
     * 默認實現類 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4);

    private final int key;

    IdType(int key) {
        this.key = key;
    }
}
  • ID_WORKER(分布式全局唯一ID 長整型類型(please use ASSIGN_ID))
  • UUID(32位UUID字符串(please use ASSIGN_UUID))
  • ID_WORKER_STR(分布式全局唯一ID 字符串類型(please use ASSIGN_ID) ) 

在一些比較的版本,還有上面三種,目前這三種已經全部過時,不推薦使用。

二、ID生成策略源碼

  在上面源碼中可以看到最常用ASSIGN_ID是通過雪花算法實現,實現類是DefaultIdentifierGenerator,源碼如下,所以可以通過閱讀借鑒實現方式用到其他框架中。DefaultIdentifierGenerator實現了IdentifierGenerator,IdentifierGenerator是ID策略獲取接口,目前只有DefaultIdentifierGenerator一個實現類。

public class DefaultIdentifierGenerator implements IdentifierGenerator {
    private final Sequence sequence;

    public DefaultIdentifierGenerator() {
        this.sequence = new Sequence(null);
    }

    public DefaultIdentifierGenerator(InetAddress inetAddress) {
        this.sequence = new Sequence(inetAddress);
    }

    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的ID生成依賴於Sequence類,Sequence工具類在碼雲上面也有獨立開源項目(Sequence碼雲地址),其實雪花算法實現原理在開源地址也說清楚了就不在搬運,如果在生產時遇到問題也可去開源評論區討論,下面是Sequence的源碼,非常值得借鑒。另外,就是關於起點時間twepoch的設定,對於某一些內網項目,可以服務器時間不正確,或者沒設置時間,就會出現ID為負數的情況,這是生成的ID二進制最高位為1造成的,所以適當的調整twepoch往前即可。

package com.baomidou.mybatisplus.core.toolkit;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 分布式高效有序 ID 生產黑科技(sequence)
 *
 * <p>優化開源項目:https://gitee.com/yu120/sequence</p>
 *
 * @author hubin
 * @since 2016-08-18
 */
public class Sequence {

    private static final Log logger = LogFactory.getLog(Sequence.class);
    /**
     * 時間起始標記點,作為基准,一般取系統的最近時間(一旦確定不能變動)
     */
    private final long twepoch = 1288834974657L;
    /**
     * 機器標識位數
     */
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    /**
     * 毫秒內自增位
     */
    private final long sequenceBits = 12L;
    private final long workerIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /**
     * 時間戳左移動位
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private final long workerId;

    /**
     * 數據標識 ID 部分
     */
    private final long datacenterId;
    /**
     * 並發控制
     */
    private long sequence = 0L;
    /**
     * 上次生產 ID 時間戳
     */
    private long lastTimestamp = -1L;
    /**
     * IP 地址
     */
    private InetAddress inetAddress;

    public Sequence(InetAddress inetAddress) {
        this.inetAddress = inetAddress;
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }

    /**
     * 有參構造器
     *
     * @param workerId     工作機器 ID
     * @param datacenterId 序列號
     */
    public Sequence(long workerId, long datacenterId) {
        Assert.isFalse(workerId > maxWorkerId || workerId < 0,
            String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
            String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 獲取 maxWorkerId
     */
    protected 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);
    }

    /**
     * 數據標識id部分
     */
    protected long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            if (null == this.inetAddress) {
                this.inetAddress = InetAddress.getLocalHost();
            }
            NetworkInterface network = NetworkInterface.getByInetAddress(this.inetAddress);
            if (null == network) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
                    id = id % (maxDatacenterId + 1);
                }
            }
        } catch (Exception e) {
            logger.warn(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }

    /**
     * 獲取下一個 ID
     *
     * @return 下一個 ID
     */
    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() {
        return SystemClock.now();
    }

}

 


免責聲明!

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



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