zoukankan      html  css  js  c++  java
  • 定时任务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;
        }
    
    }
  • 相关阅读:
    卫星时间同步装置的安装及售后
    windowsU盘重装系统:操作流程
    vue安装正确流程
    win10以太网未识别的网络
    [UnityShader]unity中2D Sprite显示阴影和接受阴影
    [UnityShader]说厌了的遮挡显示
    [Unity]利用Mesh绘制简单的可被遮挡,可以探测的攻击指示器
    ConcurrentHashMap源码解读
    Vector底层原理
    LinkedList集合底层原理
  • 原文地址:https://www.cnblogs.com/UncleWang001/p/9987104.html
Copyright © 2011-2022 走看看