天天看點

Redis分布式緩存鎖

1.Redis分布式鎖

用的Redis來實作分布式鎖最簡單的方式是在執行個體裡建立一個鍵值,建立出來的鍵值有一個逾時時間,是以每個鎖最終會被釋放,當一個用戶端想要釋放鎖時候,用戶端隻要删除這個鍵值就可以。

利用redis的腳本編寫申請和釋放鎖代碼比利用WATCH / MULTI / EXEC編寫的代碼更加簡潔,減少了業務伺服器客戶伺服器之間的互動,在高并發情況下redis的腳本編寫代碼比WATCH / MULTI / EXEC性能上高了一倍以上,以下為Redis的腳本

擷取鎖

if redis.call('exists',KEYS[1])==false then
    return redis.call('setex',KEYS[1],unpack(ARGV))
end
           

釋放鎖

if redis.call('get',KEYS[1])==ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return -1
end
           

這個分布式鎖存在如下的問題 

1.如果redis伺服器當機,整個系統都将處于不可用狀态,如果使用了redis叢集又會引入新的問題,redis的主和奴隸節點之間同步資料需要時間,看如下的一個例子:

  1. 用戶端A在主節點上拿到了鎖

    a_source_lock

  2. 從在節點還沒來得及同步

    a_source_lock

    時主節點當機于是主切換到從
  3. 用戶端乙向新的主節點成功申請鎖,于是兩個用戶端同時擁有了

    a_source_lock

2。SETNX是一個耗時操作,因為它需要查詢這個鍵是否存在,就算redis的的百萬的QPS,在高并發的場景下,這種操作也是有問題的

與的MySQL資料庫鎖相比,上述緩存鎖性能更好,在redis的叢集狀态下如果容忍主節點當機時同一資源被多個線程持有的話,這種鎖也是一種不錯的選擇

2. RedisLock算法

在分布式版本的算法裡面我們假設有N個Redis Master節點,這些節點都是互相獨立的,也就是保證N個節點不會同時當機。通常将N設為5,算法流程如下

  1. 擷取目前的毫秒時間
  2. 輪流用相同的KEY和随機值在Ñ個節點上請求鎖,用戶端在每個主上請求鎖時,會有一個和總的鎖釋放時間相比小的多的逾時時間,目的是為了防止向一個當機的redis的伺服器請求太長時間導緻真正鎖使用的時間太短。比如鎖自動釋放的時間是10秒,那麼每個節點鎖請求逾時舌尖應該設定在5〜50ms的。如果一個主不可用,盡快嘗試向其它主申請
  3. 用戶端計算第二步中擷取鎖花費的時間,隻有當用戶端在大多數主節點上擷取了鎖,并且鎖釋放的時間大于擷取鎖的時間,那麼這個鎖就是有效的
  4. 如果鎖擷取的時間成功了,那現在鎖自動釋放的時間就是最初鎖釋放的時間減去之前擷取鎖所消耗的時間
  5. 如果鎖擷取失敗,則用戶端需要向每個主節點釋放鎖

2.1失敗後重試

當用戶端擷取鎖失敗時候,這個用戶端應該在一個随機延時後重試,之是以采用随機的延時是為了避免并發情況下,多個用戶端同時重試,每個用戶端隻拿到<(N + 1)/ 2的鎖(這樣的鎖都是失敗的鎖)。對于失敗的鎖,線程要及時釋放

3.Redis緩存鎖總結

redlock解決了第一種redis的分布式鎖存在的問題,并且擁有較高的可靠性,但是所有的逾時鎖包括上述第一種的Redis分布式鎖和上篇文章讨論的MySQL的分布式鎖都存在一個問題,在FULL GC情況下或者遠端服務調用情況下,導緻整個應用阻塞,鎖因為逾時自動釋放,但是應用本身未感覺,認為自己仍然持有鎖,這就出問題了

RedLock複雜性相比第一種Redis的鎖更高,主要展現在以下幾點

  1. 首先必須部署5個主節點實作RedLock的高可靠性
  2. 同時擷取一半以上的Redis伺服器的緩存鎖,這一步耗時更長
  3. 5個節點,如果某2個節點當機必須等待逾時時間之後才能傳回,并且要擷取到剩下3個節點的所有的鎖,難度也大大增大
  4. 如果出現網絡分區,那麼可能出現用戶端永遠也無法擷取鎖的情況

轉自:https://blog.csdn.net/JavaMoo/article/details/78637479 

繼續閱讀