定時任務redis鎖+自定義lambda優化提取冗餘程式碼
阿新 • • 發佈:2018-11-20
功能介紹:
我係統中需要跑三個定時任務,由於是多節點部署,為了防止多個節點的定時任務重複執行。所以在定時任務執行時加個鎖,搶到鎖的節點才能執行定時任務,沒有搶到鎖的節點就不執行。從而避免了定時任務重複執行的情況
沒有使用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; } }