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

原文地址:https://www.cnblogs.com/gabin/p/15716724.html