Redisson分佈式鎖有效性測試

Redis是三主三從集羣模式

node:ip1:port1,ip2:port2,ip3:port3,ip4:port4,ip5:port5,ip6:port6,

spring boot 2.1.7.RELEASE ;redission版本:3.11.6

   @Bean
    public RedissonClient getRedisson(){
        Config config = new Config();
        for(String node:nodes.split(",")){
            config.useClusterServers().addNodeAddress("redis://"+node);
        }
        config.useClusterServers().setMasterConnectionPoolSize(200);
        config.useClusterServers().setSlaveConnectionMinimumIdleSize(200);
        config.useClusterServers().setMasterConnectionMinimumIdleSize(200);
        config.useClusterServers().setSlaveConnectionPoolSize(200);
        config.useClusterServers().setScanInterval(3000);
        config.useClusterServers().setPassword(password);
        return Redisson.create(config);
    }

測試加鎖代碼1:

@RequestMapping("/lockTest")
    public String lockTest() throws Exception {        
        RLock rl=redissonClient.getFairLock("myTestLock");
        if(rl.tryLock(5,30,TimeUnit.SECONDS)) {
            String name=Thread.currentThread().getName();
            System.out.println(name +" get lock at "+  System.currentTimeMillis());          
            return "YES";
        }
        return "NO";
    }

 

使用壓測工具:Jmeter

運行35S,最終controller打印獲取鎖的情況是:

http-nio-9100-exec-6 get lock at 1588661240669
http-nio-9100-exec-6 get lock at 1588661240784
http-nio-9100-exec-6 get lock at 1588661240808
http-nio-9100-exec-6 get lock at 1588661240825
http-nio-9100-exec-6 get lock at 1588661242079
http-nio-9100-exec-6 get lock at 1588661242101
http-nio-9100-exec-6 get lock at 1588661242137
http-nio-9100-exec-6 get lock at 1588661242162
http-nio-9100-exec-6 get lock at 1588661242180
http-nio-9100-exec-6 get lock at 1588661242198

。。。。。

表面上看:鎖的有效期是30S,爲啥壓測35s獲取到鎖是N次?

首先分清楚問題:誰持有鎖?什麼情況能再次獲取到鎖?

問題關係到的對象有:客戶端請求,tomcat線程池,Redission客戶端,Redis集羣服務器。

參考資料:https://mp.weixin.qq.com/s/y_Uw3P2Ll7wvk_j5Fdlusw

Redission客戶端會綁定鎖到線程ID,同一線程如果是鎖的持有者,執行加鎖的時候會增加加鎖次數,以及重新延長鎖的有效時間,watchdog根據redisson客戶端配置的scanInterval定時檢測鎖的有效期。

理解以上的原理,就很好解釋了,tomcat線程池裏面的線程是tomcat根據自己的算法分配到每一個請求,線程如果是未執行完的狀態是不會分配給下一個請求的,如果已執行完並且是第一個鎖的持有者的線程分配到該請求,那麼該請求一定能夠獲取到鎖。

上述示例代碼的加鎖後執行的代碼時間極短,無論壓測持續多久,獲取到鎖的線程ID始終是同一個:第一個獲取到鎖的線程。

只要是請求分配到了第一個獲取到鎖的線程,那麼就能獲取到鎖,而且每次獲取到鎖之後,鎖的有效期都會重新刷新爲30S.

 

生產問題代碼:

public void lockDemo(){

    RLock lock=redissionClient.getLock("XXX");

   if(!lock.tryLock(5, 300, TimeUnit.SECONDS)){
                result.put("errorCode", "ERR01");
                result.put("errorMsg", "請勿重複操作");
                return result;
    }    

    查詢業務結果是否已處理,已處理則拋出異常

    final RLock lock1=lock;

    taskExecutor.execute(new Runnable(){

          final RLock lockF = lock1;

           執行主幹業務。

           lockF .unlock();

   }

}

 

在測試環境單機模式下,一個人開多個瀏覽器窗口,同時點擊,業務會偶爾出現重複處理現象,而且處理完畢後,unlock處理後鎖依然未釋放。

根據上述測試得出的結論:

1.主幹加鎖操作很快執行完畢,主幹加鎖業務是在主線程完成的,業務處理卻是在異步的線程完成,

Redission要求加鎖和解鎖操作必須在同一個線程內完成,兩者線程ID不相等,所以導致鎖始終無法釋放,只能等待鎖的有效期過期。

2.由於主線程執行時間很快,第一次獲取到鎖的線程A在下一個同樣的請求到達之後,由於線程A是已完結狀態,Redission認爲業務也已完結,只要線程A在主幹的異步線程尚未完成處理時,又被分配給了下一個請求,tryLock是可以成功的。測試環境併發不大,而且測試環境是單機模式,所以出現的概率也會很高。

 

解決方案:1.如果不會引起客戶端超時的情況下,將異步改爲同步。

                   2.如果會引起客戶端超時,業務處理複雜,加鎖和業務處理都異步執行,客戶端生成UUID,客戶端不斷輪訓此UUID來查詢業務處理結果狀態。每個客戶端重複處理同一個業務,但是請求ID不一樣,都可以查到自己的處理結果。

 

測試案例2:

@RequestMapping("/lockTest")
    public String lockTest() throws Exception {        
        RLock rl=redissonClient.getFairLock("XXH122232");
        if(rl.tryLock(1,30,TimeUnit.SECONDS)) {
            String name=Thread.currentThread().getName();
            System.out.println(name +" get lock at "+  System.currentTimeMillis());
            Thread.currentThread().sleep(20000);
            return "YES";
        }
        return "NO";
    }

Jmeter持續壓測時間<20s,本地啓動多個APP應用,測試應用集羣模式+Ression集羣模式下鎖的有效性,在原有的基礎上增加一個http Request。9000端口應用和9100端口應用連同一個redis集羣。

 

壓測結果:

http-nio-9100-exec-1 get lock at 1588663319488

只有其中一個APP應用獲取到鎖,另外的一個應用沒有任何打印。

驗證結果:鎖在應用APP集羣模式+Redis集羣模式下是有效的。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章