业务场景:有一个统计报表的task(定时)业务,业务大概就是统计数据然后存入数据库中,在线上环境部署到两台不同tomcat上,就是防止我一个task失败之后,另一个也会跑。
像这种业务的话,我们要实现会遇到一些问题:
1、有可能两个task同时跑,这样的话就会造成两个不同的进程会同时向数据库中写入,会造成数据混乱。
为了解决这个问题,我们就可以使用redis锁来解决。
解决思路:
SETNX命令(这个命令就是当我向redis存入相同key值的时候,如果这个key存在,则返回0表示存入失败,如果不存在,则返回1表示存入成功)。当一个进程进来就往redis里面写入一个标识,如果写入成功就执行我的报表统计业务,如果失败就不做任何处理,直接结束。这样就保证一个进程在执行这块代码的同时,其他的进程是不会执行的。这样就不会出现插入数据库数据混乱。
代码实现:
@Override
public void initReportData() {
String id = UUID.randomUUID().toString();
try{
//当多个进程进来 会先判断初始化报表的redis表示是否存在,存在则写入失败,返回false,否则返回true可以执行初始化报表
if(redisUtil.setIfNull("lockReport:allReportInit:agreeOneBegin",id,Long.valueOf(1200))) {
//同步数据之前 清空报表和首页相关redis缓存
redisUtil.removePattern("report:*");
redisUtil.removePattern("homepage:*");
//初始化 指标使用情况统计数据和建设类型情况统计数据
initReportIndexUsageStatistics();
//初始化 需求取消情况统计数据
initReportCancelStatistics();
//初始化 分公司批次指标各阶段数据明细
initReportBatchIndexStateDetailStatisticsQuik();
}
}catch (Exception e){
logger.error("初始化报表失败:",e);
}finally {
//清空key为:report:allReportInit:agreeOneBegin的redis缓存
String currentId = (String) redisUtil.get("lockReport:allReportInit:agreeOneBegin");
if(id.equals(currentId)){//如果redis标识的值跟id的值相等,就说明是执行报表同步的进程,这个时候就删除redis标识
redisUtil.removePattern("lockReport:allReportInit:agreeOneBegin");
}
}
}
代码解释:
redisUtil是封装的一个操作redis的公共类。redisUtil.setIfNull()方法跟SETNX方法差不多,就是写入redis成功返回true,写入失败返回false。存入的redis表示key就是自定义的:lockReport:allReportInit:agreeOneBegin,value就是uuid生成的。在执行报表统计代码的这个进程执行完了之后,不管执行是否成功都会进入finally中,并且要把value取出来和id比较,如果相等就是同一个进程,这个时候就要删除这个redis标识。这样的话后续的进程进来发现redis没有这个标识就会去执行报表统计的代码。