实现思路:
项目中需要对异步操作的多个任务进行计数统计,且在这些任务完成时对任务执行情况进行保存,分布式环境下本地计数器显然不能适用了,于是考虑使用redis的Hash数据类型进行存储计数。Redisson的RMapCache对象提供的api便捷操作k,v结构数据同时兼具定期淘汰元素的机制,在其基础上做了一个计数器,记录一下。
1.初始化计数器,设置元素过期时间
/**
* 初始化计数器
* @param batchId
* @param totalCount
*/
public void initCounter(Long batchId,Integer totalCount){
RMapCache<String, Integer> mapCache = redissonClient.getMapCache(mapKey + batchId,
IntegerCodec.INSTANCE);
mapCache.put(totalKey + batchId, totalCount, 24, TimeUnit.HOURS);
mapCache.put(countKey + batchId, 0, 24, TimeUnit.HOURS);
mapCache.put(successKey + batchId, 0, 24, TimeUnit.HOURS);
mapCache.put(failKey + batchId, 0, 24, TimeUnit.HOURS);
}
2.执行计数统计
/**
* 执行成功计数
* true:全部执行完成,false:未执行完成
* @param batchId
* @return
*/
public Boolean executeCount(Long batchId,boolean successFlag){
RMapCache<String, Integer> mapCache = redissonClient.getMapCache(mapKey+batchId, IntegerCodec.INSTANCE);
if (successFlag){
mapCache.addAndGet(successKey+batchId,1);
}else {
mapCache.addAndGet(failKey+batchId,1);
}
if (mapCache.addAndGet(countKey+batchId,1).equals(mapCache.get(totalKey+batchId))){
log.info("任务执行完成,batchId is {}",batchId);
return Boolean.TRUE;
}else {
return Boolean.FALSE;
}
}
3.查询执行情况
/**
* 查询执行结果
*/
public CountResult queryCounter(Long batchId) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache(mapKey + batchId,
IntegerCodec.INSTANCE);
return CountResult.builder()
.total(mapCache.get(totalKey + batchId))
.count(mapCache.get(countKey + batchId))
.success(mapCache.get(successKey + batchId))
.fail(mapCache.get(failKey + batchId))
.build();
}
4.销毁RMapCache
/**
* 销毁RMapCache
*/
public void destroy(Long batchId) {
RMapCache<String, Integer> mapCache = redissonClient.getMapCache(mapKey + batchId,
IntegerCodec.INSTANCE);
mapCache.destroy();
}
遇到的问题:
1.对获取到的RMapCache对象进行addAndGet操作时抛出异常,提示ERR Error running script (call to f_d00b594f5607f3847d85ee785709a65f57f98fff): @user_script:1: user_script:1: attempt to perform arithmetic on a nil value. channel,原因是在获取RMapCache对象时没有定义编码解码方式,增加参数IntegerCodec.INSTANCE后解决。
2.调用RMapCache对象的destroy()api后,redis上的数据并未清除,反复验证得出结论:调用destroy()方法后只会删除ConcurrentMap<String, EvictionTask> tasks中的任务,避免任务过多产生OOM,redis上的数据并不会清除,调用RMap接口的remove可以达到实时删除redis数据的目的,只调用destory()方法时,redis上的数据将在过期失效后清除。