zoukankan      html  css  js  c++  java
  • 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)、千万别乱搞服务器的时间,反正谁我也查不出来

  • 相关阅读:
    MATLAB读取文件——从非常规文本文件中读取数据
    注意——CAN通信设备控制
    硬件——USB传输速度和物理接口
    STM32F4-浮点DSP库的MDK开发环境的设置
    CRC校验
    蓝牙串口使用心得
    Mysql 层级、执行顺序、执行计划分析
    讲一讲垃圾回收算法
    【转】Java中的新生代、老年代、永久代和各种GC
    工具链接
  • 原文地址:https://www.cnblogs.com/gabin/p/15716724.html
Copyright © 2011-2022 走看看