一、背景
每天会拉取数据库中的任务,保存到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;
}
三、变量的更新
image.pngxxl-job每天00:00:00会调用该job,更新bucket的数值。
@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,盯了很久才发现。
因为开发和测试环境,都复现不出来这个坑。
网友评论