2021年12月21日復盤 雪花算法 服務器時鍾偏移錯誤


1、Mybatis plus默認對id為空的進行雪花算法或者UUID生成,但是我用的是select seq.nextval from dual的方式獲取的,這個本來也就是浪費資源去生成ID而已。但是沒想到。。。。服務器的時間居然比實際的時間多了20多秒,結果雪花算法在計算ID值的時候,判斷上一次的時間戳始終大於本次生成的一直報錯。

 錯誤堆棧大概是這樣的:

Caused by: java.lang.RuntimeException: Clock moved backwards.  Refusing to generate id for 24227 milliseconds
        at com.baomidou.mybatisplus.core.toolkit.Sequence.nextId(Sequence.java:158) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator.nextId(DefaultIdentifierGenerator.java:45) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator.nextId(DefaultIdentifierGenerator.java:27) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at com.baomidou.mybatisplus.core.MybatisParameterHandler.populateKeys(MybatisParameterHandler.java:133) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at com.baomidou.mybatisplus.core.MybatisParameterHandler.process(MybatisParameterHandler.java:112) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at java.util.ArrayList.forEach(ArrayList.java:1249) ~[?:1.8.0_121]
        at com.baomidou.mybatisplus.core.MybatisParameterHandler.processParameter(MybatisParameterHandler.java:79) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at com.baomidou.mybatisplus.core.MybatisParameterHandler.<init>(MybatisParameterHandler.java:64) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver.createParameterHandler(MybatisXMLLanguageDriver.java:35) ~[mybatis-plus-core-3.4.1.jar:3.4.1]
        at org.apache.ibatis.session.Configuration.newParameterHandler(Configuration.java:645) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.executor.statement.BaseStatementHandler.<init>(BaseStatementHandler.java:69) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.executor.statement.PreparedStatementHandler.<init>(PreparedStatementHandler.java:41) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.executor.statement.RoutingStatementHandler.<init>(RoutingStatementHandler.java:46) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.session.Configuration.newStatementHandler(Configuration.java:658) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:48) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) ~[mybatis-3.5.6.jar:3.5.6]
        at sun.reflect.GeneratedMethodAccessor121.invoke(Unknown Source) ~[?:?]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_121]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_121]
        at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49) ~[mybatis-3.5.6.jar:3.5.6]
        at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:83) ~[mybatis-plus-extension-3.4.1.jar:3.4.1]
        at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) ~[mybatis-3.5.6.jar:3.5.6]
        at com.sun.proxy.$Proxy176.update(Unknown Source) ~[?:?]
        at sun.reflect.GeneratedMethodAccessor121.invoke(Unknown Source) ~[?:?]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_121]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_121]
        at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63) ~[mybatis-3.5.6.jar:3.5.6]
        at com.sun.proxy.$Proxy176.update(Unknown Source) ~[?:?]
        at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) ~[mybatis-3.5.6.jar:3.5.6]
        at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184) ~[mybatis-3.5.6.jar:3.5.6]
        at sun.reflect.GeneratedMethodAccessor161.invoke(Unknown Source) ~[?:?]
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_121]
        at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_121]
        at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427) ~[mybatis-spring-2.0.6.jar:2.0.6]
        ... 85 more

分析下源碼com.baomidou.mybatisplus.core.toolkit.Sequence

    /**
     * 獲取下一個 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;
    }

 

上述方法的報錯位置是

 

我們往上看這個if分支, 

 

 

再看一下lastTimestamp這個變量,最終賦值為本次的當前時間戳

 

 

動一下小腦袋,發現意思就是說,出現了服務器時間往前多跑了一段時間后,又往后跑了。

 

由於維護的服務器是從一個內部設置的NTP服務器上同步的時間,所以得出一個假設

 

有人TMD改了服務器時間,先增后減,觸發了這個雪花算法ID值獲取的BUG

 

系統只有跑到當時多跑的時間之后,才會正常,或者重啟一下(因為這個上一次的時間戳保存在內存里面)

 

解決方案:

1)、由於我的系統不需要使用雪花算法,所以,我直接配置idType為none(之所以會觸發id生成器是一個默認的邏輯,主要是因為我的主鍵是直接通過sql,selct seq.nexval from dual的方式在執行sql的時候獲取的,所以才會觸發這個問題)

2)、如果可以用數據庫自增也行,Oracle其實也可以指定值從序列取,但是需要配置idType,一般就是

@TableId(type = IdType.AUTO)

3)、千萬別亂搞服務器的時間,反正誰我也查不出來

 


免責聲明!

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



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