1. 程式人生 > >定時任務redis鎖+自定義lambda優化提取冗餘程式碼

定時任務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;
    }

}