Redission鎖的設計原理和應用

Redission鎖的設計原理和應用

一:基本使用方法

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.8.2</version>
</dependency>
@Test
public void tt() {
    Config config = new Config();
    config.useSingleServer().setAddress("127.0.0.1:6399").setDatabase(0);
    // 構造RedissonClient
    RedissonClient redissonClient = Redisson.create(config);
    // 設置鎖定資源名稱
    RLock disLock = redissonClient.getLock("helloRedissionLock");
    boolean isLock;
    try {
        //嘗試獲取分佈式鎖
        isLock = disLock.tryLock(500, 15000, TimeUnit.MILLISECONDS);
        if (isLock) {
            //TODO if get lock success, do something;
            Thread.sleep(15000);
        }
    } catch (Exception e) {
    } finally {
        // 不管如何, 最後都要解鎖
        disLock.unlock();
    }
}

二:經過源碼解讀設計原理

2.1獲取鎖(關鍵代碼以下)
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
          "if (redis.call('exists', KEYS[1]) == 0) then " +
              "redis.call('hset', KEYS[1], ARGV[2], 1); " +
              "redis.call('pexpire', KEYS[1], ARGV[1]); " +
              "return nil; " +
          "end; " +
          "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
              "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
              "redis.call('pexpire', KEYS[1], ARGV[1]); " +
              "return nil; " +
          "end; " +
          "return redis.call('pttl', KEYS[1]);",
            Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

解讀:這是一段lua的腳本java

第一個if:判斷key = helloRedissionLock是否存在?若不存(==0)在則經過hset命令設置 key = helloRedissionLock的hash對象(key=當前線程ID:1 value=1),而且設置過時時間redis

第二個if:判斷key = helloRedissionLock和hash對象(key=當前線程ID:1 value=1)?若存在(==1)則:1.設置hash對象的value+1,2.從新設置過時時間spring

return:key = helloRedissionLock的過時時間數據庫

備註:Hincrby 命令用於爲哈希表中的字段值加上指定增量值,該點是reentrant lock的關鍵編程

2.2 解鎖
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
        "if (redis.call('exists', KEYS[1]) == 0) then " +
            "redis.call('publish', KEYS[2], ARGV[1]); " +
            "return 1; " +
        "end;" +
        "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
            "return nil;" +
        "end; " +
        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
        "if (counter > 0) then " +
            "redis.call('pexpire', KEYS[1], ARGV[2]); " +
            "return 0; " +
        "else " +
            "redis.call('del', KEYS[1]); " +
            "redis.call('publish', KEYS[2], ARGV[1]); " +
            "return 1; "+
        "end; " +
        "return nil;",
        Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

解讀:springboot

第一個if:判斷key=入參是否存在?若不存在(==0),廣播機制,而後返回1。多線程

第二個if:判斷key和value是否存在?若不存在(==0 返回ni l。分佈式

腳本(存在):local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);獲取valueui

第三個if:判斷counter是否大於0?true-->value-1:lua

else:刪除key並廣播

2.3 等待
while (true) {
    long currentTime = System.currentTimeMillis();
    ttl = tryAcquire(leaseTime, unit, threadId);
    // lock acquired
    if (ttl == null) {
        return true;
    }
		......
}

解讀:

經過while(true)實現線程等待

三:應用實例(Springboot)

3.1 簡介:在分佈式或者多線程場景下,爲了讓一個數據或方法(數據來源數據庫)在同一時刻只能讓一我的處理(即串性),咱們能夠利用Redission鎖讓只有拿到鎖的人才能編輯該數據。

實現原理:利用切面編程+註解(RedissionLock)編程的方式實現對方法上加入了RedissionLock註解的方法進行全局加鎖

3.2 定義註解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissionLock {
    /**
     * 鎖住方法中的第幾個參數(-1所有參數)
     *
     * @return
     */
    int lockIndexParam() default -1;

    /**
     * 鎖的時間
     *
     * @return
     */
    int leaseTime() default 10;

    /**
     * 等待時間
     */
    int waitTime() default 5;
}

3.3 註冊切面

@Aspect
@Component
public class RedissionLockAspect {
    @Resource
    private RedissonClient redissonClient;

    /**
     * 環繞通知:靈活自由的在目標方法中切入代碼
     */
    @Around("@annotation(redissionLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedissionLock redissionLock) throws Throwable {
        // 獲取目標方法的名稱
        String methodName = joinPoint.getSignature().getName();
        // 獲取方法傳入參數
        Object[] params = joinPoint.getArgs();
        System.out.println("==@Around== lingyejun blog logger --》 method name " + methodName + " args " + params[0]);
        RLock lock = redissonClient.getLock(params[redissionLock.lockIndexParam()-1].toString());
        boolean b = lock.tryLock(redissionLock.waitTime(), redissionLock.leaseTime(), TimeUnit.MINUTES);
        if (b) {
            lock.unlock();
            return joinPoint.proceed();
        } else {
            return null;
        }

    }
 }

3.4 使用方式

@RedissionLock(lockIndexParam = 1)
public void tt(String id){
    System.out.println("進入了方法::" + id);
}
相關文章
相關標籤/搜索