如何使用注解方式實(shí)現(xiàn)?Redis?分布式鎖
引入 Redisson
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.14.1</version> </dependency>
初始化 Redisson
@Configuration
public class RedissonConfiguration {
// 此處更換自己的 Redis 地址即可
@Value("${redis.addr}")
private String addr;
@Bean
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer()
.setAddress(String.format("%s%s", "redis://", addr))
.setConnectionPoolSize(64) // 連接池大小
.setConnectionMinimumIdleSize(8) // 保持最小連接數(shù)
.setConnectTimeout(1500) // 建立連接超時(shí)時(shí)間
.setTimeout(2000) // 執(zhí)行命令的超時(shí)時(shí)間, 從命令發(fā)送成功時(shí)開始計(jì)時(shí)
.setRetryAttempts(2) // 命令執(zhí)行失敗重試次數(shù)
.setRetryInterval(1000); // 命令重試發(fā)送時(shí)間間隔
return Redisson.create(config);
}
}這樣我們就可以在項(xiàng)目里面使用 Redisson 了。
編寫 Redisson 分布式鎖工具類
Redis 分布式鎖的工具類,主要是調(diào)用 Redisson 客戶端實(shí)現(xiàn),做了輕微的封裝。
@Service
@Slf4j
public class LockManager {
/**
* 最小鎖等待時(shí)間
*/
private static final int MIN_WAIT_TIME = 10;
@Resource
private RedissonClient redisson;
/**
* 加鎖,加鎖失敗拋默認(rèn)異常 - 操作頻繁, 請(qǐng)稍后再試
*
* @param key 加鎖唯一key
* @param expireTime 鎖超時(shí)時(shí)間 毫秒
* @param waitTime 加鎖最長等待時(shí)間 毫秒
* @return LockResult 加鎖結(jié)果
*/
public LockResult lock(String key, long expireTime, long waitTime) {
return lock(key, expireTime, waitTime, () -> new BizException(ResponseEnum.COMMON_FREQUENT_OPERATION_ERROR));
}
/**
* 加鎖,加鎖失敗拋異常 - 自定義異常
*
* @param key 加鎖唯一key
* @param expireTime 鎖超時(shí)時(shí)間 毫秒
* @param waitTime 加鎖最長等待時(shí)間 毫秒
* @param exceptionSupplier 加鎖失敗時(shí)拋該異常,傳null時(shí)加鎖失敗不拋異常
* @return LockResult 加鎖結(jié)果
*/
private LockResult lock(String key, long expireTime, long waitTime, Supplier<BizException> exceptionSupplier) {
if (waitTime < MIN_WAIT_TIME) {
waitTime = MIN_WAIT_TIME;
}
LockResult result = new LockResult();
try {
RLock rLock = redisson.getLock(key);
try {
if (rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS)) {
result.setLockResultStatus(LockResultStatus.SUCCESS);
result.setRLock(rLock);
} else {
result.setLockResultStatus(LockResultStatus.FAILURE);
}
} catch (InterruptedException e) {
log.error("Redis 獲取分布式鎖失敗, key: {}, e: {}", key, e.getMessage());
result.setLockResultStatus(LockResultStatus.EXCEPTION);
rLock.unlock();
}
} catch (Exception e) {
log.error("Redis 獲取分布式鎖失敗, key: {}, e: {}", key, e.getMessage());
result.setLockResultStatus(LockResultStatus.EXCEPTION);
}
if (exceptionSupplier != null && LockResultStatus.FAILURE.equals(result.getLockResultStatus())) {
log.warn("Redis 加鎖失敗, key: {}", key);
throw exceptionSupplier.get();
}
log.info("Redis 加鎖結(jié)果:{}, key: {}", result.getLockResultStatus(), key);
return result;
}
/**
* 解鎖
*/
public void unlock(RLock rLock) {
try {
rLock.unlock();
} catch (Exception e) {
log.warn("Redis 解鎖失敗", e);
}
}
}加鎖結(jié)果狀態(tài)枚舉類。
public enum LockResultStatus {
/**
* 通信正常,并且加鎖成功
*/
SUCCESS,
/**
* 通信正常,但獲取鎖失敗
*/
FAILURE,
/**
* 通信異常和內(nèi)部異常,鎖狀態(tài)未知
*/
EXCEPTION;
}加鎖結(jié)果類封裝了加鎖狀態(tài)和RLock。
@Setter
@Getter
public class LockResult {
private LockResultStatus lockResultStatus;
private RLock rLock;
}自此我們就可以使用分布式鎖了,使用方式:
@Service
@Slf4j
public class TestService {
@Resource
private LockManager lockManager;
public String test(String userId) {
// 鎖:userId, 鎖超時(shí)時(shí)間:5s, 鎖等待時(shí)間:50ms
LockResult lockResult = lockManager.lock(userId, 5000, 50);
try {
// 業(yè)務(wù)代碼
} finally {
lockManager.unlock(lockResult.getRLock());
}
return "";
}
}為了防止程序發(fā)生異常,所以每次我們都需要在finally代碼塊里手動(dòng)釋放鎖。為了更方便優(yōu)雅的使用 Redis 分布式鎖,我們使用注解方式實(shí)現(xiàn)下。
聲明注解 @Lock
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Lock {
/**
* lock key
*/
String value();
/**
* 鎖超時(shí)時(shí)間,默認(rèn)5000ms
*/
long expireTime() default 5000L;
/**
* 鎖等待時(shí)間,默認(rèn)50ms
*/
long waitTime() default 50L;
}注解解析類
@Aspect
@Component
@Slf4j
public class LockAnnotationParser {
@Resource
private LockManager lockManager;
/**
* 定義切點(diǎn)
*/
@Pointcut(value = "@annotation(Lock)")
private void cutMethod() {
}
/**
* 切點(diǎn)邏輯具體實(shí)現(xiàn)
*/
@Around(value = "cutMethod() && @annotation(lock)")
public Object parser(ProceedingJoinPoint point, Lock lock) throws Throwable {
String value = lock.value();
if (isEl(value)) {
value = getByEl(value, point);
}
LockResult lockResult = lockManager.lock(getRealLockKey(value), lock.expireTime(), lock.waitTime());
try {
return point.proceed();
} finally {
lockManager.unlock(lockResult.getRLock());
}
}
/**
* 解析 SpEL 表達(dá)式并返回其值
*/
private String getByEl(String el, ProceedingJoinPoint point) {
Method method = ((MethodSignature) point.getSignature()).getMethod();
String[] paramNames = getParameterNames(method);
Object[] arguments = point.getArgs();
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(el);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < arguments.length; i++) {
context.setVariable(paramNames[i], arguments[i]);
}
return expression.getValue(context, String.class);
}
/**
* 獲取方法參數(shù)名列表
*/
private String[] getParameterNames(Method method) {
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
return u.getParameterNames(method);
}
private boolean isEl(String str) {
return str.contains("#");
}
/**
* 鎖鍵值
*/
private String getRealLockKey(String value) {
return String.format("lock:%s", value);
}
}下面使用注解方式使用分布式鎖:
@Service
@Slf4j
public class TestService {
@Lock("'test_'+#user.userId")
public String test(User user) {
// 業(yè)務(wù)代碼
return "";
}
}當(dāng)然也可以自定義鎖的超時(shí)時(shí)間和等待時(shí)間
@Service
@Slf4j
public class TestService {
@Lock(value = "'test_'+#user.userId", expireTime = 3000, waitTime = 30)
public String test(User user) {
// 業(yè)務(wù)代碼
return "";
}
}到此這篇關(guān)于如何使用注解方式實(shí)現(xiàn) Redis 分布式鎖的文章就介紹到這了,更多相關(guān)Redis 分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何解決Redis緩存穿透(緩存空對(duì)象、布隆過濾器)
緩存穿透是一個(gè)常見的問題,它發(fā)生當(dāng)請(qǐng)求的數(shù)據(jù)既不在緩存中也不在數(shù)據(jù)庫中,文章通過一個(gè)查詢商品店鋪的案例,展示了如何結(jié)合這兩種方法來避免緩存穿透,首先利用布隆過濾器過濾掉不存在的id,對(duì)于誤判的情況,則采用緩存空對(duì)象的策略進(jìn)行補(bǔ)救2024-11-11
Redis實(shí)現(xiàn)和數(shù)據(jù)庫的數(shù)據(jù)同步
本文介紹了Redis與傳統(tǒng)數(shù)據(jù)庫數(shù)據(jù)同步的幾種常見方法,包括CacheAside、WriteThrough、WriteBehind,以及如何通過分布式事務(wù)、樂觀鎖、數(shù)據(jù)過期策略和消息隊(duì)列來解決數(shù)據(jù)一致性問題,每種方法都有其適用場景和優(yōu)缺點(diǎn),需要根據(jù)具體需求進(jìn)行選擇2025-01-01
關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹
大家好,本篇文章主要講的是關(guān)于Redis數(shù)據(jù)庫三種持久化方案介紹,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
redis中RedissonLock如何實(shí)現(xiàn)等待鎖的
本文主要介紹了redis中RedissonLock如何實(shí)現(xiàn)等待鎖的,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11

