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

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

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

一、背景

每天会拉取数据库中的任务,保存到redis的set集合。考虑任务的数量过大的时候,redis出现大key,所以我们拆分出N个bucket, 让每个key保存一部分的任务。


image.png
  • 现状:每天0点0时0分,按照当天的任务数不同,而计算bucket。也就是前一天的bucket数是5,第二天可能就变成了4。好处是动态变化,但是我们的这个值使用了类的变量来存储。带来的一个副作用是在更新它的时候,没有考虑分布式场景,仅单个节点才是正确的。

二、类变量的声明

public class Constants {
    /**
     * 冷数据bucket的数量,会每天动态的修改
     */
    public static Integer BUCKET_SIZE = 1;
}

三、变量的更新

xxl-job每天00:00:00会调用该job,更新bucket的数值。

image.png
    @XxlJob("coldCacheRefresher")
    public ReturnT<String> execute(String s) {
            // 统计当天的任务总数
            long todayTaskSize = taskRepo.getCountForTodayTask();

            //根据当日任务的数量,动态的设置需要多少个bucket,让每个bucket存储1000个任务
            Constants.BUCKET_SIZE = (int) todayTaskSize / 1000 + 1;

            log.info("今日的bucket为{}", Constants.BUCKET_SIZE);
      }

四、任务放入redis缓存

回应文章开头所讲,我们需要将同一天的key,进一步拆分为多个bucket。

// 任务的内容求hash值,然后模上总bucket数,求得所处bucket的序号
  int iBucket = Math.abs(taskBase.getContent().hashCode() % Constants.BUCKET_SIZE);

// DateUtil.getDateStrForToday()求得当天的日期yyyy-MM-dd
  String key = String.format(Constants.TASK_COLD, DateUtil.getDateStrForToday(), iBucket);

  String value = JsonUtils.toJsonString(new TaskDTO(taskBase));

  redisOperator.getRedisTemplate().opsForSet().add(key, value);
    
 //冷数据设置24h过期
  this.setExpireTime(key, 24, TimeUnit.HOURS);

五、删除任务--删除redis缓存

删除任务,和保存任务类似,根据任务的内容求得hash,然后模上bucket数,得到这一天的某个bucket。

public boolean delTaskFromColdCache(TaskDTO taskDTO) {
        int iBucket = Math.abs(taskDTO.getTaskContent().hashCode() % Constants.BUCKET_SIZE);
        String key = String.format(Constants.TASK_COLD, DateUtil.getDateStrForToday(), iBucket);

        String value = JsonUtils.toJsonString(new TaskDTO(taskDTO.getTaskContent(), taskDTO.getNotifyTime(), taskDTO.getBackUrl()));

        redisOperator.getRedisTemplate().opsForSet().remove(key, value);
        return true;
    }

六、问题总结

// 删除任务,出现失败,因为redis的key,不是最新的值
taskRedisOperator.delTaskFromColdCache(oldTask);

xxl-job只会在某一天触发更新某个jvm节点的Constants.BUCKET_SIZE,当前后两天的总任务数计算出来的bucket数不同,就会出现分布式场景下的不一致问题。

七、解决办法

  • 采用固定的bucket数,缘于任务数相对稳定
  • 把计算出来的bucket数,存放在分布式的redis数据库,而不是jvm内存。

最后,呼吁不要采用这种写法,隐藏太深了。如果真的是要使用类的变量,怎么也不要写到常量类,至少要写个封装类。

这种写法的缺点是它很容易踩到分布式的数据不一致的坑,单看代码逻辑很对,还是偶现的bug,盯了很久才发现。

因为开发和测试环境,都复现不出来这个坑。

相关文章

网友评论

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

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