前言
分布式鎖想必大家并不陌生:控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。
如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機(jī)之間共享了一個或一組資源,那么訪問這些資源的時候,往往需要互斥來防止彼此干擾來保證一致性,在這種情況下,便需要使用到分布式鎖。
實(shí)現(xiàn)分布式鎖的方式多種多樣,但一般來說都是使用編碼的方式在業(yè)務(wù)代碼中穿插加鎖解鎖邏輯。
這種方式優(yōu)點(diǎn)是十分模版化,幾乎不會出錯,每一套加鎖解鎖邏輯都是一模一樣。
缺點(diǎn)也是這個,雖說ctrl c, ctrl v很爽,但一直ctrl c, ctrl v也會讓人感到痛苦。
所以,如何基于該思想實(shí)現(xiàn)一把注解版的分布式鎖呢?
實(shí)現(xiàn)目標(biāo)
業(yè)務(wù)舉例:下單功能為防止用戶重復(fù)下單,短時間內(nèi)只允許用戶一次點(diǎn)擊成功。
@RedisLock(name = "order", keys = {"#userId"})
public String order(Long userId){
return "ok";
}
如代碼所示:只需在方法上加上注解,即可實(shí)現(xiàn)分布式鎖。
name: 鎖的名稱
keys: 該業(yè)務(wù)唯一性資源標(biāo)識,比如這里是短時間內(nèi)某個用戶只能下一次單,所以keys為用戶id
name + keys 組合成redis中的key
設(shè)計
大多數(shù)由注解設(shè)計的功能都是通過切面完成的,這個也不例外。
最簡單的實(shí)現(xiàn)方式就是將原始代碼的加鎖解鎖邏輯拷貝到切面中實(shí)現(xiàn)。
如原始代碼如下:
@Resource
private RedissonClient redissonClient;
public String order(Long userId){
String key = "order" + userId;
RLock lock = redissonClient.getLock(key);
try {
// 嘗試加鎖, true表示加鎖成功
if (lock.tryLock()) {
// 業(yè)務(wù)代碼
return "ok";
}
return "fail";
}finally {
// 是否為該線程持有鎖
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
顯然,除了return ok這行代碼,其他的都是模板代碼。
redissonClient: redisson框架的客戶端,官方文檔:https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D
AOP切面設(shè)計如下:

編碼
1、定義注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
/**
* key的名稱前綴,與keys字段共同拼接出一個redis key
* 如 name = user keys = 123(userId)
* 最終使用的key為 user123
*/
String name();
/**
* 能夠確定出系統(tǒng)中唯一性資源的key
* 如 用戶, 使用用戶id為key
* 或者使用 用戶名+手機(jī)號
* 必須為spel表達(dá)式 如 #id #user.id #user.name
*/
String[] keys();
}
2、編寫切面
@Aspect
public class RedisLockAspect {
@Resource
private RedissonClient redissonClient;
@Around(value = "@annotation(redisLock)")
public Object lock(ProceedingJoinPoint joinPoint, RedisLock redisLock) throws Throwable {
// 拼接出key 使用spel解析注解中定義的key
String key = getRedisKey(joinPoint, redisLock);
// 獲取鎖
RLock lock = redissonClient.getLock(key);
try {
// 加鎖
if (lock.tryLock()) {
return joinPoint.proceed();
}
throw new RuntimeException("加鎖失敗");
}finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
一個超級簡單的注解版Redis鎖就實(shí)現(xiàn)了
讀者:“啪!你就寫個這破玩意敷衍我呢!”
“別別別,我錯了,你聽我繼續(xù)說呀!“
組件庫
以上內(nèi)容只是傳播思想,對,思想。
好用的注解版Redis鎖我已經(jīng)實(shí)現(xiàn)了。并且已經(jīng)將它發(fā)布到了Maven中央倉庫。
關(guān)于它的詳細(xì)文檔還請各位移步:https://github.com/lzj960515/kq-universal/tree/main/kq-universal-redis-starter
如果你想知道其中的實(shí)現(xiàn),歡迎clone代碼閱讀或者與我交流
同時,我還想給大伙介紹一下組件庫:https://github.com/lzj960515/kq-universal
該組件庫為我司內(nèi)部的后端組件庫——部分組件,里面的組件已經(jīng)久經(jīng)考驗(yàn),可以放心使用。
使用這些組件,可以將你的編碼效率提升...反正總是會有提升的,我用著是特爽。
如果你想?yún)⑴c進(jìn)來,歡迎之至,如果你發(fā)現(xiàn)里面有bug,感謝大佬!