Grails 自定義Id生成策略(雪花算法)

參考

參考官網自定義id生成器

class CustomGenerator implements IdentifierGenerator {

    SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator()

    @Override
    Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        def id = snowflakeIdGenerator.nextId()
        println id
        return id
    }

    @Override
    boolean supportsJdbcBatchInserts() {
        return super.supportsJdbcBatchInserts()
    }
}

 

代碼裏面的SnowflakeIdGenerator 爲參考雪花算法實現的java類

package utils;

import org.springframework.beans.factory.annotation.Value;

import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.List;
import java.util.Random;

/**
* @Description:    id生成策略 雪花算法(改進版本)
* @Author:         [email protected]
* @CreateDate:     2020/2/28 21:21
* @UpdateUser:     [email protected]
* @UpdateDate:     2020/2/28 21:21
* @UpdateRemark:   修改內容
* @Version:        1.0
*/
public class SnowflakeIdGenerator {
    /**
     * 業務線標識id所佔的位數
     **/
    private final long serviceIdBits = 8L;
    /**
     * 業務線標識支持的最大數據標識id(這個移位算法可以很快的計算出幾位二進制數所能表示的最大十進制數)
     */
    private final long maxServiceId = -1L ^ (-1L << serviceIdBits);

    @Value("${snowflake.serviceId}")
    private long serviceId;


    /**
     * 機器id所佔的位數
     **/
    private final long workerIdBits = 10L;
    /**
     * 支持的最大機器id
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    private final long workerId;


    /**
     * 序列在id中佔的位數
     **/
    private final long sequenceBits = 7L;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);


    /**
     * 開始時間戳(2018年1月1日)
     **/
    private final long twepoch = 1514736000000L;
    /**
     * 最後一次的時間戳
     **/
    private volatile long lastTimestamp = -1L;
    /**
     * 毫秒內序列
     **/
    private volatile long sequence = 0L;
    /**
     * 隨機生成器
     **/
    private static volatile Random random = new Random();


    /**
     * 機器id左移位數
     **/
    private final long workerIdShift = sequenceBits;
    /**
     * 業務線id左移位數
     **/
    private final long serviceIdShift = workerIdBits + sequenceBits;
    /**
     * 時間戳左移位數
     **/
    private final long timestampLeftShift = serviceIdBits + workerIdBits + sequenceBits;

    public SnowflakeIdGenerator() {
        if ((serviceId > maxServiceId) || (serviceId < 0)) {
            throw new IllegalArgumentException(String.format("service Id can't be greater than %d or less than 0", maxServiceId));
        }
        workerId = getWorkerId();
        if ((workerId > maxWorkerId) || (workerId < 0)) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
    }

    public SnowflakeIdGenerator(long serviceId) {
        if ((serviceId > maxServiceId) || (serviceId < 0)) {
            throw new IllegalArgumentException(String.format("service Id can't be greater than %d or less than 0", maxServiceId));
        }
        workerId = getWorkerId();
        if ((workerId > maxWorkerId) || (workerId < 0)) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        this.serviceId = serviceId;
    }

    public synchronized long nextId() throws IllegalArgumentException {
        long timestamp = System.currentTimeMillis();
        if (timestamp < lastTimestamp) {
            throw new IllegalArgumentException("Clock moved backwards.  Refusing to generate id for " + (
                    lastTimestamp - timestamp) + " milliseconds.");
        }
        //如果是同一時間生成的,則進行毫秒內序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //跨毫秒時,序列號總是歸0,會導致序列號爲0的ID比較多,導致生成的ID取模後不均勻,所以採用10以內的隨機數
            sequence = random.nextInt(10) & sequenceMask;
        }
        //上次生成ID的時間截(設置最後時間戳)
        lastTimestamp = timestamp;

        //移位並通過或運算拼到一起組成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //時間戳
                | (serviceId << serviceIdShift) //業務線
                | (workerId << workerIdShift) //機器
                | sequence; //序號
    }

    /**
     * 等待下一個毫秒的到來, 保證返回的毫秒數在參數lastTimestamp之後
     * 不停獲得時間,直到大於最後時間
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 根據機器的MAC地址獲取工作進程Id,也可以使用機器IP獲取工作進程Id,取最後兩個段,一共10個bit
     * 極端情況下,MAC地址後兩個段一樣,產品的工作進程Id會一樣;再極端情況下,併發不大時,剛好跨毫秒,又剛好隨機出來的sequence一樣的話,產品的Id會重複
     *
     * @return
     */
    protected long getWorkerId() throws IllegalArgumentException {
        try {
            java.util.Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces();
            while (en.hasMoreElements()) {
                NetworkInterface iface = en.nextElement();
                List<InterfaceAddress> addrs = iface.getInterfaceAddresses();
                for (InterfaceAddress addr : addrs) {
                    InetAddress ip = addr.getAddress();
                    NetworkInterface network = NetworkInterface.getByInetAddress(ip);
                    if (network == null) {
                        continue;
                    }
                    byte[] mac = network.getHardwareAddress();
                    if (mac == null) {
                        continue;
                    }
                    long id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 11;
                    if (id > maxWorkerId) {
                        return new Random(maxWorkerId).nextInt();
                    }
                    return id;
                }
            }
            return new Random(maxWorkerId).nextInt();
        } catch (SocketException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * 獲取序號
     *
     * @param id
     * @return
     */
    public static Long getSequence(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        String sequenceBinary = str.substring(size - 7, size);
        return Long.parseLong(sequenceBinary, 2);
    }

    /**
     * 獲取機器
     *
     * @param id
     * @return
     */
    public static Long getWorker(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        String sequenceBinary = str.substring(size - 7 - 10, size - 7);
        return Long.parseLong(sequenceBinary, 2);
    }

    /**
     * 獲取業務線
     *
     * @param id
     * @return
     */
    public static Long getService(Long id) {
        String str = Long.toBinaryString(id);
        int size = str.length();
        String sequenceBinary = str.substring(size - 7 - 10 - 8, size - 7 - 10);
        return Long.parseLong(sequenceBinary, 2);
    }


    public static void main(String[] args) {
        SnowflakeIdGenerator snowflakeIdGenerator = new SnowflakeIdGenerator();
        System.out.println(snowflakeIdGenerator.nextId());
    }
}

在application.groovy裏全局設置id生成方式

grails.gorm.default.mapping = {
	id column: 'id', generator:'utils.CustomGenerator'
}

 

注意:如果使用了Spring Security 安全插件,並且使用Requestmap數據庫映射權限的,需要注意Requestmap的id策略不可以改變,否則創建權限的時候,權限刷新代碼不起作用

//不起作用
springSecurityService.clearCachedRequestmaps()

具體沒有去深入查詢原因,Requestmap表的映射如下

static mapping = {
    id   generator: "identity"
	cache true
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章