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)、千萬別亂搞服務器的時間,反正誰我也查不出來