如何使用注解實(shí)現(xiàn)分布式鎖

前言

分布式鎖想必大家并不陌生:控制分布式系統(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è)計如下:

lock_design.png

編碼

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,感謝大佬!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容