定时任务redis锁+自定义lambda优化提取冗余代码

功能介绍:

我系统中需要跑三个定时任务,由于是多节点部署,为了防止多个节点的定时任务重复执行。所以在定时任务执行时加个锁,抢到锁的节点才能执行定时任务,没有抢到锁的节点就不执行。从而避免了定时任务重复执行的情况

没有使用lambda表达式时的代码是这样的:

   @Scheduled(cron = "${task.syncIncrement}")
    private void syncIncrementComment() {
     //获取redis锁,并把当前时间放入redis,锁定lockSeconds秒【插入之前判断是否已经有lockName了,如果存在则获取锁失败】
boolean lockKeyResult = redisTemplateHandler.redisSetNX(lockName, String.valueOf(Calendar.getInstance().getTimeInMillis()), lockSeconds); if (lockKeyResult) {//如果获取锁成功,执行业务代码 LOGGER.info("catch Redis-Task lock"); long startTime = System.currentTimeMillis(); LOGGER.info("开始同步原始数据..."); handler.getInterfaceCommentByCond(0); LOGGER.info("原始数据同步完成!用时:" + (System.currentTimeMillis() - startTime));
       //业务代码执行完成,释放锁
boolean delResult = redisTemplateHandler.redisDelNX(lockName); LOGGER.info("free Redis-Task lock: {}", delResult); } else {//获取锁失败 LOGGER.info("do not catch Redis-Task lock"); if (!redisTemplateHandler.redisCheckNX(lockName, lockSeconds)) { //根据时间判断redis锁是否是失效锁, 执行锁失效,造成死锁 LOGGER.info("Redis-Task lock"); boolean redel = redisTemplateHandler.redisDelNX(lockName); // 释放执行锁 LOGGER.info("free Redis-Task lock: {}", redel); } } }

灰色部分就是对定时任务加的redis锁,可以看出,如果我要写10个定时任务那就要写十遍这些代码。这显然是不优雅的。所以我就想能不能把模板代码提取出来呢?然后把我们的要执行的业务代码当做参数传进来,这样的话我们就不用重复编写这些模板代码。而只需要关注我们的业务代码就好。

解决方案就是函数式接口->lambda表达式

改造:

1.编写函数式接口

@FunctionalInterface
public interface RedisLockFunction {
    public void excuteMonitor();
}

2.提取模板代码

 public void excuteInRedisLock(String lockName,RedisLockFunction lock) {
        boolean lockKeyResult = redisTemplateHandler.redisSetNX(lockName,
                String.valueOf(Calendar.getInstance().getTimeInMillis()), lockSeconds);
        if (lockKeyResult) {
            lock.excuteMonitor();  //业务代码,就这一行
            boolean delResult = redisTemplateHandler.redisDelNX(lockName);
            LOGGER.info("free Redis-Task lock: {}", delResult);
        } else {
            LOGGER.info("do not catch Redis-Task lock");
            if (!redisTemplateHandler.redisCheckNX(lockName, lockSeconds)) { // 执行锁失效,造成死锁
                LOGGER.info("Redis-Task lock");
                boolean redel = redisTemplateHandler.redisDelNX(lockName); // 释放执行锁
                LOGGER.info("free Redis-Task lock: {}", redel);
            }
        }
    }

3.调用,可以与上面的做对比

 @Scheduled(cron = "${task.syncIncrement}")
    private void syncIncrementComment() {
        excuteInRedisLock(WebConstants.TASK_LOCK_SYNCINCREMENTCOMMENT_HANDLE_MESSAGE,()->{
            LOGGER.info("catch Redis-Task lock");
            long startTime = System.currentTimeMillis();
            LOGGER.info("开始同步原始数据...");
            handler.getInterfaceCommentByCond(0);
            LOGGER.info("原始数据同步完成!用时:" + (System.currentTimeMillis() - startTime));
        });
    }

这样就实现了把代码当做参数传递到一个方法中取执行的功能。从而实现了代码的复用。

附redis锁的工具类代码

package com.ch.evaluation.handler;

import java.util.Calendar;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisTemplateHandler {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    /**
     * 插入分布式Job Redis锁
     */
    public boolean redisSetNX(String key, String val, long expire) {
        boolean result = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            return connection.setNX(key.getBytes(), val.getBytes());
        });
        if (result) {
            stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS);
        }
        return result;
    }
    
    /**
     * 删除分布式Job Redis锁
     */
    public boolean redisDelNX(String key) {
        boolean result = stringRedisTemplate.delete(key);
        return result;
    }
    
    /**
     * 检查分布式Job Redis锁
     */
    public boolean redisCheckNX(String key, int lockSeconds) {
        long expireTime = stringRedisTemplate.getExpire(key);
        String nxValue = stringRedisTemplate.opsForValue().get(key);
        long time = 0;
        if (StringUtils.isNotBlank(nxValue)) {
            time = Calendar.getInstance().getTimeInMillis() - Long.valueOf(nxValue).longValue();
        }
        if (expireTime <= 0 
                || time > lockSeconds * 1000L) {
            redisDelNX(key);
            return false;
        }
        return true;
    }

}
原文地址:https://www.cnblogs.com/UncleWang001/p/9987104.html