一、说在前面的话
本文的问题,需要理解前文https://www.jianshu.com/p/550462158846,发现的问题是通知延迟,原本应该是13点15分回调给业务方的,结果硬是拖到了14点才执行。
延迟的原因初步发现是,没有及时将冷数据区移入至热数据区。
还有一个地方需要解释的是,为什么在13点和13点30分的时候遗漏掉了,而在14点的时候又变成正确的了。
这也就是程序员经常说的偶现问题!!
其实就上文也提到的问题。https://www.jianshu.com/p/fe39f9f486e3
幸运的是,我们每一步的数据转移, 都有充足的日志来给我们证明。根据业务上的唯一标识,可以清晰看出它的流转全过程。
二、取证过程
1、xxl-job之每日拉取当日任务数据到冷数据缓存中
image.png这个任务会更新bucketSize的值,但是只会更新某个jvm节点。
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任务的路由策略是轮询。
五、总结
当bucket_size由多变少,只会到处拉取冷数据缓存的时候,空转而已。
但是,当bucket_size由少变多,当xxl-job执行到的节点,bucket_size还是脏数据的时候,就会出现遗漏。
再一次证明是偶现的问题吧!!
网友评论