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)、千万别乱搞服务器的时间,反正谁我也查不出来