业务场景:有一个统计报表的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没有这个标识就会去执行报表统计的代码。