基於redis實現分佈式鎖遇到的一個坑

背景

之前舊代碼中使用redis的incr來實現分佈式鎖。
邏輯大概是線上諸多線程去獲取inrc一個key,第一個成功的則得到返回值1。其他線程如果inc這個變量則返回2,3,4,5…
成功返回的線程,然後再對這個key設值一個過期時間30秒。代碼已經在線上跑了幾個月了,沒啥毛病。

遇到問題

這個問題是在測試環境遇到的,什麼情況呢?就是沒有線程可以成功獲取這個key了,換句話說,就是這個key永遠不等於1了。
查詢了下redis,發現已經增加到幾萬了,用ttl看返回的是-1,也就是說,生命週期是永久了。

排查

對代碼邏輯仔細排查,未發現不妥的邏輯。
後來,想了想,只有一種情況會出現這樣的情況,那就是第一個成功incr key的線程,在進行incr後,在沒來得及設值過期的時候,線程就掛了,可能是程序崩了,可能是被測試同學在啓動工程的時候,停掉了,或者部署工程其他分支等。反正就是掛了。
所以這一點就違背了分佈式鎖的一個條件(無死鎖)。

分佈式鎖需要具備哪些條件?
互斥性:在任意一個時刻,只有一個客戶端持有鎖。
無死鎖:即便持有鎖的客戶端崩潰或者其他意外事件,鎖仍然可以被獲取。
容錯:只要大部分Redis節點都活着,客戶端就可以獲取和釋放鎖

驗證想法

在redis del 掉這個key後,發現ttl又正常了,值也正常了。

原因

因爲這裏有兩個操作,第一步incr(key),第二步expire(key)
所以, 雖然發生錯誤的機率很小,但是就是有機會發生,就是線程掛了。至於什麼原因引起的,亂七八糟的外界原因。

改進

改用redis的set().
通過set操作的原子性來保證,當然還有其他方式,比如通過eval lua腳本等。這裏使用了比較簡單的方式。

 String result = jedis.set(lockKey, lockvalue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

在這裏插入圖片描述

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