分佈式鎖, 註解形式, 搞定SpringBoot定時任務@Scheduled 在集羣下的優化

原文鏈接:https://blog.csdn.net/weixin_38399962/article/details/82182267

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/weixin_38399962/article/details/82182267

SpringBoot提供了 Schedule模塊完美支持定時任務的執行

在實際開發中由於項目部署在分佈式或集羣服務器上 會導致定時任務多次觸發

因此,使用redis分佈鎖機制可以有效避免多次執行定時任務

核心方法是org.springframework.data.redis.core包下的

setIfAbsent() 方法 返回值爲布爾類型

方法類似redis的SETNX命令 即”SET if Not Exists”

服務器在執行郵件定時發送任務之前會向redis緩存中寫入lock_key即任務鎖 表明此服務器正在執行定時任務

另一臺服務器在寫入鎖時 由於鎖已經存在就不做任何操作

執行定時任務的服務器在執行完成後需釋放任務鎖

具體代碼實現如下:

定義註解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {
 
    String lockPrefix() default "";
    String lockKey() default "";
    long timeOut() default 5;
    TimeUnit timeUnit() default TimeUnit.SECONDS;
 
}

定義切面@Aspect, pointCut就是 RedisLock註解

@Aspect
@Component
public class RedisLockAspect {
 
    private static final Integer MAX_RETRY_COUNT = 3;
    private static final String LOCK_PRE_FIX = "lockPreFix";
    private static final String LOCK_KEY = "lockKey";
    private static final String TIME_OUT = "timeOut";
    private static final int PROTECT_TIME = 2 << 11;//4096
 
    private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);
 
    @Autowired
    private CommonRedisHelper commonRedisHelper;
 
 
    @Pointcut("@annotation(com.shuige.components.cache.annotation.RedisLock)")
    public void redisLockAspect() {
    }
 
    @Around("redisLockAspect()")
    public void lockAroundAction(ProceedingJoinPoint proceeding) throws Exception {
      
        //獲取redis鎖
        Boolean flag = this.getLock(proceeding, 0, System.currentTimeMillis());
        if (flag) {
            try {
                proceeding.proceed();
                Thread.sleep(PROTECT_TIME);
            } catch (Throwable throwable) {
                throw new RuntimeException("分佈式鎖執行發生異常" + throwable.getMessage(), throwable);
            } finally {
                // 刪除鎖
                this.delLock(proceeding);
            }
        } else {
            log.info("其他系統正在執行此項任務");
        }
 
    }
 
    /**
     * 獲取鎖
     *
     * @param proceeding
     * @return
     */
    private boolean getLock(ProceedingJoinPoint proceeding, int count, long currentTime) {
        //獲取註解中的參數
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
        String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
        String key = (String) annotationArgs.get(LOCK_KEY);
        long expire = (long) annotationArgs.get(TIME_OUT);
        //String key = this.getFirstArg(proceeding);
        if (StringUtils.isEmpty(lockPrefix) || StringUtils.isEmpty(key)) {
            // 此條執行不到
            throw new RuntimeException("RedisLock,鎖前綴,鎖名未設置");
        }
        if (commonRedisHelper.setNx(lockPrefix, key, expire)) {
            return true;
        } else {
            // 如果當前時間與鎖的時間差, 大於保護時間,則強制刪除鎖(防止鎖死)
            long createTime = commonRedisHelper.getLockValue(lockPrefix, key);
            if ((currentTime - createTime) > (expire * 1000 + PROTECT_TIME)) {
                count ++;
                if (count > MAX_RETRY_COUNT){
                    return false;
                }
                commonRedisHelper.delete(lockPrefix, key);
                getLock(proceeding,count,currentTime);
            }
            return false;
        }
    }
 
    /**
     * 刪除鎖
     *
     * @param proceeding
     */
    private void delLock(ProceedingJoinPoint proceeding) {
        Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
        String lockPrefix = (String) annotationArgs.get(LOCK_PRE_FIX);
        String key = (String) annotationArgs.get(LOCK_KEY);
        commonRedisHelper.delete(lockPrefix, key);
    }
 
    /**
     * 獲取鎖參數
     *
     * @param proceeding
     * @return
     */
    private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
        Class target = proceeding.getTarget().getClass();
        Method[] methods = target.getMethods();
        String methodName = proceeding.getSignature().getName();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Map<String, Object> result = new HashMap<String, Object>();
                RedisLock redisLock = method.getAnnotation(RedisLock.class);
                result.put(LOCK_PRE_FIX, redisLock.lockPrefix());
                result.put(LOCK_KEY, redisLock.lockKey());
                result.put(TIME_OUT, redisLock.timeUnit().toSeconds(redisLock.timeOut()));
                return result;
            }
        }
        return null;
    }
 
    /**
     * 獲取第一個String類型的參數爲鎖的業務參數
     *
     * @param proceeding
     * @return
     */
    @Deprecated
    public String getFirstArg(ProceedingJoinPoint proceeding) {
        Object[] args = proceeding.getArgs();
        if (args != null && args.length > 0) {
            for (Object object : args) {
                String type = object.getClass().getName();
                if ("java.lang.String".equals(type)) {
                    return (String) object;
                }
            }
        }
        return null;
    }
 
}

CommonRedisHelper

@Component
public class CommonRedisHelper {
 
    @Autowired
    RedisTemplate redisTemplate;
 
    /**
     * 加分佈式鎖
     *
     * @param track
     * @param sector
     * @param timeout
     * @return
     */
    public boolean setNx(String track, String sector, long timeout) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
 
        Boolean flag = valueOperations.setIfAbsent(track + sector, System.currentTimeMillis());
        // 如果成功設置超時時間, 防止超時
        if (flag) {
            valueOperations.set(track + sector, getLockValue(track, sector), timeout, TimeUnit.SECONDS);
        }
        return flag;
    }
 
    /**
     * 刪除鎖
     *
     * @param track
     * @param sector
     */
    public void delete(String track, String sector) {
        redisTemplate.delete(track + sector);
    }
 
    /**
     * 查詢鎖
     * @return 寫鎖時間
     */
    public long getLockValue(String track, String sector) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        long createTime = (long) valueOperations.get(track + sector);
        return createTime;
    }
 
}

使用場景:

@Scheduled(cron = "0,30 * * * * ? ")
    @RedisLock(lockPrefix = "hello",lockKey = "world")
    public void hello(){
        System.out.println("每隔30秒定時任務測試,當前時間爲:" + new SimpleDateFormat("yyyy年MM月dd日 HH時mm分ss秒").format(new Date()));
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章