美文网首页
延迟任务通知服务的线上问题(二)

延迟任务通知服务的线上问题(二)

作者: 天草二十六_简村人 | 来源:发表于2023-01-04 15:53 被阅读0次

    一、说在前面的话

    本文的问题,需要理解前文https://www.jianshu.com/p/550462158846,发现的问题是通知延迟,原本应该是13点15分回调给业务方的,结果硬是拖到了14点才执行。
    延迟的原因初步发现是,没有及时将冷数据区移入至热数据区。

    还有一个地方需要解释的是,为什么在13点和13点30分的时候遗漏掉了,而在14点的时候又变成正确的了。

    这也就是程序员经常说的偶现问题!!
    其实就上文也提到的问题。https://www.jianshu.com/p/fe39f9f486e3

    幸运的是,我们每一步的数据转移, 都有充足的日志来给我们证明。根据业务上的唯一标识,可以清晰看出它的流转全过程。

    二、取证过程

    1、xxl-job之每日拉取当日任务数据到冷数据缓存中

    这个任务会更新bucketSize的值,但是只会更新某个jvm节点。

    image.png

    2、jvm日志,体现了内存变量bucketSize的值

    因为线上是部署了两个节点,一个是正确的,另一个则是脏数据。

    container_ip:10.224.170.195
    2023-01-02 00:00:00.076 INFO [task-notification,,,] 9 --- [ Thread-1030] c.x.c.n.i.job.ColdCacheRefresherJob : 今日的keySize为2

    container_ip:10.224.169.197
    2023-01-03 00:00:00.082 INFO [task-notification,,,] 8 --- [ Thread-1049] c.x.c.n.i.job.ColdCacheRefresherJob : 今日的keySize为4

    container_ip:10.224.169.197
    2023-01-04 00:00:00.083 INFO [task-notification,,,] 8 --- [ Thread-1074] c.x.c.n.i.job.ColdCacheRefresherJob : 今日的keySize为4

    container_ip:10.224.170.195
    2023-01-05 00:00:00.078 INFO [task-notification,,,] 9 --- [ Thread-1122] c.x.c.n.i.job.ColdCacheRefresherJob : 今日的keySize为2

    3、xxl-job之冷数据缓存移至热数据缓存区

    image.png
    • 2023-01-04 13:00:00 执行节点的address:http://10.224.170.195:19033/ 因为这个节点的bucket_size还是1月2日更新的值(为2),它循环不到冷缓存数据的redis key。

    • 2023-01-04 13:30:00 执行节点的address:http://10.224.170.195:19033/ 同上,仍旧不会把冷数据区移入热数据区

    • 2023-01-04 14:00:00 执行节点的address:http://10.224.169.197:19033/ 因为这个节点的bucket_size是4,而任务{"contentId":"E0GRKEKK","cmd":2,"invalid":0} 所处的redis key是TNT:Task:Code:2023-01-04:3,所以才轮询到。(注意key的末尾值是3,它是大于2,小于4)


      image.png

    三、源码

    外围是一个for循环,遍历当天所有的分片。Constants.BUCKET_SIZE的值如果不对,会导致程序未遍历到,也就不会把冷数据过滤至热数据区。

    @XxlJob("hotCacheRefresher")
        public ReturnT<String> execute(String s) {
            for (int i = 0; i < Constants.BUCKET_SIZE; i++) {
                String coldKey = String.format(Constants.TASK_COLD, DateUtil.getDateStrForToday(), i);
                try {
                    Set<String> coldDataSet = taskRedisOperator.getValuesForSet(coldKey);
                    //筛选出热数据
                    Set<String> hotDataSet = this.selectHotData(coldDataSet);
    
                    if (!CollectionUtils.isEmpty(hotDataSet)) {
                        log.info("筛选出的热数据为[coldKey={}\t hotDataSet={}]", coldKey, JsonUtils.toJsonString(hotDataSet));
                        //将筛选出的热数据,存入热数据缓存中
                        taskRedisOperator.addDataToHotCache(hotDataSet);
                        //从冷数据缓存里删除
                        taskRedisOperator.removeDatasFromCache(coldKey, hotDataSet);
                    }
                } catch (Exception e) {
                    log.error("从冷数据缓存中筛选热数据失败。[key={}]", coldKey, e);
                }
            }
            return ReturnT.SUCCESS;
        }
    
    /**
         * 从冷数据中筛选热数据
         *
         * @param coldDataSet
         */
        private Set<String> selectHotData(Set<String> coldDataSet) {
            Set<String> hotDataSet = Sets.newHashSet();
            long systemTime = System.currentTimeMillis();
            coldDataSet.forEach(data -> {
                TaskDTO taskDTO = JsonUtils.parseObject(data, TaskDTO.class);
                // 判断通知时间是否符合热数据
                if (taskDTO.getNotifyTime() < systemTime + 1800 * 1000) {
                    hotDataSet.add(data);
                }
            });
            return hotDataSet;
        }
    

    四、未发版前的解决办法

    利用重试的机制,在调度时间前,让xxl-job调度到正确的jvm节点。

    提高它的轮询频率,由30分钟调整为10分钟。
    cron表达式:0 */30 * * * ? 修改为--》 0 */10 * * * ?
    同时确定xxl-job任务的路由策略是轮询。

    image.png

    五、总结

    当bucket_size由多变少,只会到处拉取冷数据缓存的时候,空转而已。

    但是,当bucket_size由少变多,当xxl-job执行到的节点,bucket_size还是脏数据的时候,就会出现遗漏。

    再一次证明是偶现的问题吧!!

    相关文章

      网友评论

          本文标题:延迟任务通知服务的线上问题(二)

          本文链接:https://www.haomeiwen.com/subject/ghgrcdtx.html