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;
        }
    
    }
  • 相关阅读:
    Python3练习题 026:求100以内的素数
    【Python3练习题 025】 一个数,判断它是不是回文数。即12321是回文数,个位与万位相同,十位与千位相同
    Python3练习题 022:用递归函数反转字符串
    Python3练习题 021:递归方法求阶乘
    【Python3练习题 020】 求1+2!+3!+...+20!的和
    【Python3练习题 019】 有一分数序列:2/1,3/2,5/3,8/5,13/8,21/13...求出这个数列的前20项之和。
    Python3练习题 018:打印星号菱形
    Python3练习题 006 冒泡排序
    【Python3练习题 017】 两个乒乓球队进行比赛,各出三人。甲队为a,b,c三人,乙队为x,y,z三人。已抽签决定比赛名单。有人向队员打听比赛的名单。a说他不和x比,c说他不和x,z比。请编程序找出三队赛手的名单。
    【Python3练习题 016】 猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘了多少。
  • 原文地址:https://www.cnblogs.com/UncleWang001/p/9987104.html
Copyright © 2011-2022 走看看