Redis分佈式鎖如何提高可用性

在編程中我們時常考慮高併發帶來的數據訪問不安全問題,那麼我們在redis中是否也要考慮呢?答案是肯定的,有人會問:redis不是單線程的嗎?對它是單線程,但是在某些情況他會出現信息更新,用戶沒有拿到最新數據,然後導致操作有誤,看下圖

我們可以看到張三和李四同時請求這個number,但是李四執行set後張三拿到的數據是沒有更新的,而後執行了set命令,這樣這個number應該是30纔對。

Redis分佈式鎖簡介

redis給我們提供了分佈式鎖,開啓鎖的指令是setnx(set if not exists),釋放鎖指令del,下面我們來嘗試

> setnx lookname mango(integer) 1> get lookname"mango"> del lookname(integer) 1> get mango(nil)

死鎖

這裏我們思考一個問題,如果我們在執行邏輯的時候出現異常了,那麼這個鎖就會一直得不到釋放,導致死鎖,所以我們想辦法讓它自己釋放,添加一個過期時間這個指令是expire key seconds,即使我們不del,他它過了這個時間會自動釋放

> setnx lookname mango(integer) 1> expire lookname 5(integer) 1> get lookname(nil)

還有一個問題,如果我在執行setnx和expire的過程中出現了異常怎麼辦?我們很快想到了用事務,但是我們這裏注意expire依賴setnx,expire這個是當setnx搶到了資源的時候我們的expire纔會執行,否則是執行不成功的,事務裏面沒有if else語句。

爲了解決這個問題,redis開源社區開發出很多分佈式鎖的lib庫,也就是說我們每次使用分佈式鎖的時候都要引入這個第三方的庫,關於這個問題作者在Redis2.8版本中加入了set擴展,把這兩個命令放在一起執行。

> set lookname mango ex 5 nxOK> get lookname"mango"> get lookname    # 5s 後(nil)

超時問題

如果我們在加鎖後到這個鎖被釋放的時間內的業務邏輯還沒有執行完,那麼這個時候出現了新的問題就是超時,比如說我只想的邏輯需要30秒,而我的鎖過期時間只有10秒,此時我的邏輯還沒有處理完,問題就來了。

不使用過期時間

解決超時問題的快捷辦法就是不使用過期時間,我們需要在key後面添加一個比較隱私的隨機數,例如name1564835、name6879425,達到減少key的碰撞,我們對這個key做上標記,等執行邏輯處理完後程序回收這個內存。

延遲迴收

如果10秒鐘不夠那麼我們可以給這個key延長回收時間,我們在key回收前判斷客戶端是否還在使用這個key,如果沒有使用這個key我們就什麼都不做,如果在使用,我們就增加回收時長,如何做?我們可以在調用端重開一個線程,監測快過期的key,客戶端可以給redis服務實例發送一個Lua腳本檢測這個key的值有沒有改變,如果沒有改變讓redis服務端延長鎖的時間。

RedLock算法

我們在一個集羣中,當主節點掛掉時,從節點會取而代之,原先第一個客戶端在主節點中申請成功了一把鎖,但是這把鎖還沒有來得及同步到從節點,主節點突然掛掉了,然後從節點變成了主節點這個新的節點內部沒有這個鎖,所以當另一個客戶端過來請求加鎖時,此時兩個客戶端都持有這個資源,問題又出現了。

但是這個問題是極小概率事件並且是非常短時間造成的,從分佈式系統角度上我們是可以容忍這個問題的。但是我們希望redis完全不受影響,可以考慮 redlock。Redlock算法是由Antirez發明的,它的流程比較複雜,不過已經有了很多開源的 library 做了良好的封裝,用戶可以拿來即用,比如 redlock-py。

redlock.Redlock()

RedLock算法原理:

1.獲取當前時間(單位是毫秒)。

2.輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裏,客戶端在每個master上請求鎖時,會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點。

3.客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖((N/2) +1),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認爲是獲取成功了。

4.如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間。

5.如果鎖獲取失敗了,不管是因爲獲取成功的鎖不超過一半(N/2+1)還是因爲總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認爲沒有獲取成功的鎖。

注意:加鎖時,它會向過半節點發送 set(key, value, nx=True, ex=xxx) 指令,只要過半節點 set 成功,那就認爲加鎖成功。釋放鎖時,需要向所有節點發送 del 指令。不過 Redlock 算法還需要考慮出錯重試、時鐘漂移等很多細節問題,同時因爲 Redlock 需要向多個節點進行讀寫,意味着相比單實例 Redis性能會下降一些,代碼額外引入第三方lib。

有興趣的童鞋可以參考:Redlock(redis分佈式鎖)原理分析​

 

一名正在搶救的coder

筆名:mangolove

CSDN地址:https://blog.csdn.net/mango_love

GitHub地址:https://github.com/mangoloveYu

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