分布式鎖在Spring Boot應用中的實現(xiàn)過程
在現(xiàn)代微服務架構(gòu)中,分布式鎖是一種常用的技術手段,用于確保在分布式系統(tǒng)中,同一時間只有一個服務實例能夠執(zhí)行某個特定的操作。
這對于防止并發(fā)問題、保證數(shù)據(jù)一致性至關重要。在Spring Boot應用中,我們可以通過自定義注解和切面的方式,來實現(xiàn)一個既簡潔又強大的分布式鎖機制。
Lock注解
首先,我們定義一個Lock注解,用于標記需要加鎖的方法。這個注解包含了鎖的鍵值、超時時間和等待時間等信息。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @author tangzx
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
/**
* 鎖的鍵值
*
* @return 鎖的鍵值
*/
String value() default "";
/**
* 鎖的鍵值
*
* @return 鎖的鍵值
*/
String key() default "";
/**
* 超時時間
*
* @return 超時時間
*/
long leaseTime() default 30L;
/**
* 等待時間
*
* @return 等待時間
*/
long waitTime() default 0L;
/**
* 超時時間單位(默認秒)
*
* @return 超時時間單位
*/
TimeUnit leaseTimeTimeUnit() default TimeUnit.SECONDS;
/**
* 等待時間單位(默認秒)
*
* @return 等待時間單位
*/
TimeUnit waitTimeTimeUnit() default TimeUnit.SECONDS;
}
LockAspect切面
接下來,我們創(chuàng)建一個LockAspect切面類,用于處理Lock注解。
這個切面會在方法執(zhí)行前嘗試獲取鎖,如果獲取成功,則執(zhí)行方法體;如果獲取失敗,則執(zhí)行相應的失敗邏輯。
import com.lock.core.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author tangzx
*/
@Slf4j
@Aspect
@Component
public class LockAspect {
@Around("@annotation(lock)")
public Object around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
String value = lock.value();
String key = lock.key();
long leaseTimeMs = lock.leaseTimeTimeUnit().toMillis(lock.leaseTime());
long waitTimeMs = lock.waitTimeTimeUnit().toMillis(lock.waitTime());
String lockKey = resolveLockKey(value, key, joinPoint);
AtomicReference<Object> result = new AtomicReference<>(null);
AtomicReference<Throwable> throwable = new AtomicReference<>(null);
RedisUtils.LockOps.execute(lockKey, leaseTimeMs, waitTimeMs, () -> {
try {
result.set(joinPoint.proceed());
} catch (Throwable t) {
throwable.set(t);
}
}, () -> {
AppLogger.append("未獲取到Lock鎖[{}]", lockKey);
throw new AppException("正在處理中,請稍后再試");
});
if (null != throwable.get()) {
throw throwable.get();
}
return result.get();
}
public String resolveLockKey(String lockName, String key, ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(key);
StandardEvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < args.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
String value = expression.getValue(context, String.class);
if (StringUtils.isNotBlank(value)) {
return lockName + ":" + value;
}
if (log.isWarnEnabled()) {
log.warn("lockName={},根據(jù)規(guī)則[key={}],未在參數(shù)中獲取到對應的值,默認使用lockName作為key", lockName, key);
}
return lockName;
}
}
RedisLockUtils工具類
最后,我們實現(xiàn)一個RedisLockUtils工具類,用于與Redis交互,實現(xiàn)鎖的獲取和釋放。
這個類會使用Redisson客戶端來簡化分布式鎖的操作。
import com.redis.utils.ServiceLocator;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* @author :Tzx
* @date :Created in 2021/8/2 18:09
* @description: redis鎖
* @version: 1.0
*/
@Slf4j
public class RedisLockUtils {
private final static String REDIS_LOCK_HANDLER_PREFIX = RedisLockUtils.class.getSimpleName().toLowerCase() + ":";
private static volatile RedissonClient redissonClient;
/**
* 獲取分布式鎖執(zhí)行
*
* @param redisKey redisKey
* @param codeToExecute 獲取鎖執(zhí)行
*/
public static void execute(String redisKey, Runnable codeToExecute) {
execute(redisKey, null, null, codeToExecute, null);
}
/**
* 獲取分布式鎖執(zhí)行
*
* @param redisKey redisKey
* @param codeToExecute 獲取鎖執(zhí)行
* @param codeIfLockNotAcquired 未獲取到鎖執(zhí)行
*/
public static void execute(String redisKey, Runnable codeToExecute, Runnable codeIfLockNotAcquired) {
execute(redisKey, null, null, codeToExecute, codeIfLockNotAcquired);
}
/**
* 獲取分布式鎖執(zhí)行
*
* @param key redisKey
* @param leaseTimeMs 鎖超時時間
* @param waitTimeMs 獲取鎖等待時間
* @param codeToExecute 獲取鎖執(zhí)行
* @param codeIfLockNotAcquired 未獲取到鎖執(zhí)行
*/
public static void execute(String key, Long leaseTimeMs, Long waitTimeMs, Runnable codeToExecute, Runnable codeIfLockNotAcquired) {
waitTimeMs = Optional.ofNullable(waitTimeMs).orElse(0L);
String lockKey = REDIS_LOCK_HANDLER_PREFIX + key;
RLock lock = getRedissonClient().getLock(lockKey);
boolean tryLock = false;
try {
if (null != leaseTimeMs && leaseTimeMs > 0L) {
tryLock = lock.tryLock(waitTimeMs, leaseTimeMs, TimeUnit.MILLISECONDS);
} else {
tryLock = lock.tryLock(waitTimeMs, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException interruptedException) {
log.warn("獲取鎖異常", interruptedException);
Thread.currentThread().interrupt();
}
if (tryLock) {
try {
codeToExecute.run();
return;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
if (log.isDebugEnabled()) {
log.debug("未獲取到鎖[{}]", key);
}
Optional.ofNullable(codeIfLockNotAcquired).ifPresent(Runnable::run);
}
private static RedissonClient getRedissonClient() {
if (null == redissonClient) {
synchronized (RedisLockUtils.class) {
if (null == redissonClient) {
redissonClient = ServiceLocator.getService(RedissonClient.class);
}
}
}
return redissonClient;
}
}
下面是一個使用Lock注解的示例,展示了如何在Spring Boot應用中實現(xiàn)分布式鎖。
假設我們有一個OrderService服務,其中包含一個方法createOrder,這個方法需要保證在多服務實例中同時只有一個能夠被執(zhí)行,以防止創(chuàng)建重復的訂單。
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Lock(value = "order", key = "#orderId", leaseTime = 10, waitTime = 5)
public void createOrder(String orderId) {
// 業(yè)務邏輯,比如創(chuàng)建訂單、保存訂單等
System.out.println("Creating order: " + orderId);
}
}
在這個示例中,createOrder方法使用了Lock注解。當這個方法被調(diào)用時,LockAspect切面會攔截這個調(diào)用,并嘗試獲取一個分布式鎖。鎖的鍵值是由key屬性的SpEL表達式計算得出的,這里使用了方法的參數(shù)orderId。leaseTime和waitTime分別設置了鎖的超時時間和等待時間。
當多個服務實例嘗試同時創(chuàng)建同一個訂單時,由于分布式鎖的存在,只有一個實例能夠成功執(zhí)行createOrder方法,其他實例將會在等待一段時間后失敗,或者執(zhí)行Lock注解中定義的失敗邏輯。
這種使用注解的方式,使得分布式鎖的集成變得非常簡單和直觀。開發(fā)者不需要關心鎖的具體實現(xiàn)細節(jié),只需要在需要加鎖的方法上添加Lock注解,并設置相應的參數(shù)即可。
通過這三個組件,我們可以在Spring Boot應用中非常優(yōu)雅地實現(xiàn)分布式鎖。Lock注解提供了一種聲明式的方式,讓開發(fā)者可以輕松地為方法添加分布式鎖。LockAspect切面確保了鎖的邏輯在方法執(zhí)行前后被正確地處理。而RedisLockUtils工具類則負責與Redis交互,確保鎖的原子性和一致性。
在實現(xiàn)這些組件時,我們還需要注意一些細節(jié),比如如何處理鎖的鍵值解析、如何處理鎖獲取失敗的情況、如何確保鎖的釋放等。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Quarkus篇入門創(chuàng)建項目搭建debug環(huán)境
這篇文章主要為大家介紹了Quarkus篇入門創(chuàng)建項目搭建debug環(huán)境,先來一套hello?world,來搭建基本的運行及調(diào)試環(huán)境吧2022-02-02
why在重寫equals時還必須重寫hashcode方法分享
首先我們先來看下String類的源碼:可以發(fā)現(xiàn)String是重寫了Object類的equals方法的,并且也重寫了hashcode方法2013-10-10
Apache?Maven3.6.0的下載安裝和環(huán)境配置(圖文教程)
本文主要介紹了Apache?Maven3.6.0的下載安裝和環(huán)境配置,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-07-07

