使用redis鎖控制定時任務的意義
? 有一次在開發一個獲取審批狀態結果的介面時,用了一個定時任務定時去拉取的操作,在這個系統中,我沒有直接接入我們的xxl-job,因為我想換一種實作方式來試一下,同時業務對定時任務的需求不高,所以我打算嘗試使用@Scheduled來實作,
? 將cron運算式的值配置在Apollo上,實作可以動態重繪的機制,這樣定時任務確實是可以跑起來的,但是一般我們的應用都是一個分布式部署的狀態,一個應用部署了多臺服務器,那么這里就出現了一個問題:就是當每臺機器上的定時任務都被觸發了,同時去拉取狀態以及更改資料,那么這里就可能導致將資料更改錯誤,引起不必要的問題,所以我們得通過手段來控制,使的到任務觸發時間,只需要一臺應用服務器去觸發任務就好了,
此處的Apollo是攜程開源的一款分布式的配置中心
單機下使用@Scheduled沒有問題哈
代碼實作
redis配置
? redis的基本配置這個自己在網上找一下就可以了,主要是用到了RedisTemlate,下面主要講解redis鎖定時任務的實作,這里是將redis獲取鎖的程序通過AOP做成了一個公用的注解組件,提高系統的擴展性以及共用性,
定義RedisUtils工具類
? redis的工具類網上很多,我這里也是摘抄的,主要看加鎖的方法,其他的方法就不粘貼了,
package com.anzz.anzzdemo.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 加強版分布式鎖
* @param key
* @param lockExpire
* @return
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public boolean lock(String key, Long lockExpire) {
// lambda 運算式
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
// 加1是因為時間戳的精度為1s,為了避免誤差,所以增加1s
long expireAt = System.currentTimeMillis() + lockExpire + 1;
Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] value = https://www.cnblogs.com/xuanhaoo/p/connection.get(key.getBytes());
if (Objects.nonNull(value) && value.length > 0) {
long expireTime = Long.parseLong(new String(value));
// 如果鎖已經過期
if (expireTime < System.currentTimeMillis()) {
// 重新加鎖,防止死鎖
byte[] oldValue = connection.getSet(key.getBytes(), String.valueOf(System.currentTimeMillis() + lockExpire + 1).getBytes());
return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
}
}
}
return false;
});
}
}
定義一個RedisLock自定義注解
package com.anzz.anzzdemo.redis;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
/**
* 重試機制:是否需要重新嘗試獲取鎖
* @return
*/
boolean needRetry() default true;
/**
* 沒有獲取到鎖是是否需要拋出例外
* @return
*/
boolean notLockNeedThrowException() default true;
/**
* 提示
* @return
*/
String errorMsg() default "系統繁忙,請稍后重試";
}
實作切面RedisLockAspect
? 注解切面,在這里,獲取到加了注解的方法,先獲取鎖,獲取到鎖才執行真正的方法,
package com.anzz.anzzdemo.redis;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
public class RedisLockAspect {
@Autowired
private RedisUtils redisUtils;
/**
* 重試次數:這里可以配置在配置中心上,此處僅為說明
*/
private int retryCount = 5;
/**
* 重試時間
*/
private long retryTime = 200;
/**
* 過期時間
*/
private long redisLockExpire;
@Around("@annotation(RedisLock)")
public Object process(ProceedingJoinPoint point) throws Throwable {
String redisKey = getRedisKey(point);
log.info("### 開始進入RedisLock切面,key:{}", redisKey);
RedisLock redisLock = getRedisLockInfo(point);
boolean needRetry = redisLock.needRetry();
if (needRetry) {
int rCount = 1;
while (!redisUtils.lock(redisKey, redisLockExpire)) {
if (rCount > retryCount) {
if (redisLock.notLockNeedThrowException()) {
throw new RuntimeException(redisLock.errorMsg());
}
return null;
}
// 執行緒等待
Thread.sleep(retryTime);
rCount++;
}
} else {
if (!redisUtils.lock(redisKey, redisLockExpire)) {
if (redisLock.notLockNeedThrowException()) {
throw new RuntimeException(redisLock.errorMsg());
}
return null;
}
}
// 獲取鎖成功
try {
Object[] args = point.getArgs();
return point.proceed(args);
} finally {
// 釋放鎖
redisUtils.del(redisKey);
}
}
/**
* 根據自身業務設定redis的key
* @param point
* @return
*/
private String getRedisKey(ProceedingJoinPoint point) {
String sufix_key = "09090";
return "XX-RE" + point.getSignature().getDeclaringTypeName() + "-" + point.getSignature().getName() + sufix_key;
}
/**
* 獲取注解及方法資訊
* @param point
* @return
* @throws NoSuchMethodError
*/
private RedisLock getRedisLockInfo(ProceedingJoinPoint point) throws NoSuchMethodException {
String methodName = point.getSignature().getName();
Class<?> classTarget = point.getTarget().getClass();
Class<?>[] params = ((MethodSignature) point.getSignature()).getParameterTypes();
Method method = classTarget.getMethod(methodName, params);
return method.getAnnotation(RedisLock.class);
}
}
總結
? 至此,通過redis分布式實作定時任務就完成啦,redis分布式鎖的使用場景還有很多,經常用在高并發環境下來保證程式的正確性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/256611.html
標籤:Java
