1. 程式人生 > >Redis學習筆記之分散式鎖

Redis學習筆記之分散式鎖

文章目錄

一、加鎖原因

在一些比較高併發的業務場景,經常聽到通過加鎖的方法實現執行緒安全。

下面簡單介紹一下

1.1 加鎖方式

資料庫鎖
資料庫本身提供了鎖機制,比如樂觀鎖、悲觀鎖等等。下面給出我之前寫的一篇部落格,介紹一下mysql資料庫的鎖機制
Mysql的鎖機制

單體環境
Java執行緒層面,Java的jdk本身就提供了,比如synchronized和ReentrantLock可重入鎖。這是實現單體環境鎖的一種方法,這裡簡單介紹一下,並不對synchronized和ReentrantLock進行詳細介紹。

分散式環境
上面介紹的都是單體環境的和資料庫層面的,下面介紹一下分散式環境的解決方法。分散式環境有兩種比較常用的解決方法,一種是通過Zookeeper實現分散式;一種是通過Redis實現分散式鎖。本部落格比較詳細地介紹一下redis分散式鎖,對於Zookeeper分散式鎖有時間再寫部落格介紹。

1.2 業務場景

為什麼加鎖?從業務來說其實就是有業務場景,技術層面是為了保證執行緒安全性,也就是說保證執行緒操作是原子操作。

下面簡單介紹一下一些業務場景

比如電商的秒殺場景,這就是一個高併發場景了,假如一個使用者購買了庫存只有10的8件商品,另外一個使用者也要購買5件商品,這兩個使用者是同時進行的,第一個使用者買了8件,庫存就只有2件了,第二個使用者再買5件,注意是和第一個使用者操作同時進行的,這時也是10-5,庫存只有5件了。假如第一個使用者搶佔了,庫存優先減8了,第一個使用者進行操作,同時進行,獲取到的庫存是10,這時就會出現業務問題了。

二、原子操作

原子操作定義

部落格介紹一下原子操作,為什麼說到原子操作呢?貌似和分散式鎖不搭邊,其實不是的,我們說加鎖,其本質目的就是為了實現執行緒操作是原子性的,也就是原子操作。

原子操作:是指不會被執行緒排程機制打斷的操作,而且期間不會有任何上下文切換(context switch)。

2.1 context switch

上面介紹一下上下文切換(context switch),上下文切換是計算機的cpu從一個任務,或者說程序,從一個任務(程序)切換到另外一個任務(程序),期間確保任務(程序)不衝突的過程。

在國外的whatis.techtarget網站有進行了比較詳細的定義
上下文切換定義

三、分散式鎖

3.1 實現方式

可以實現方式 setnx+expire

在Redis中實現分散式鎖,可以通過setnx和expire實現,setnx命令意思是set key if not exist,就是說已經有一個執行緒佔用了,就不執行set key操作
語法:setnx key value;expire是設定key的時間

這裡setnx key為tkey,value為tvalue

>setnx tkey tvalue
OK
>get tkey
tvalue
>expire tkey 5
OK
>del tkey
(integer) 1

先setnx,然後再給key加一個時間5秒,5秒後自動釋放鎖。當然一個程序執行過程還沒5秒也可以就直接刪除key。那麼假如在setnx過程出現異常,鎖就不能釋放。

為了避免上面所說的分散式鎖不能釋放問題,開源社群有很多分散式解決方案,很多第三方庫,直到redis2.8版本,作者給出了一個很好的解決方案。

redis2.8版本對setnx命令和expire命令進行了拓展,使這兩個命令可以同時執行,也可以理解為同個事務了。

語法:

setnx key ex time nx

例子,設定tkey,時間為5秒

setnx tkey ex 5 nx

四、分散式鎖常見問題

4.1 超時問題

假如在釋放鎖和另一個執行緒重新佔用鎖之間,執行時間過長,超過了鎖的超時設定,這時候就會出現,第一個執行緒的鎖已經被標記為過期了,可是在臨界區的執行程式還沒執行,也就是說鎖並沒有真正釋放。這時候如果第二個執行緒持有了鎖,就會出現臨界區的程式碼不能正常序列執行,因為第一個執行緒的鎖在臨界區還沒真正釋放。

這是一種比較常見的Redis鎖不能釋放的超時問題。

通過網上資料,有提供了一種解決方案思路,是通過在set key的時候給value值加一個時間戳字串或者一個特定的隨機數,比如uuid,可以表示特定執行緒的標識,這個標識要唯一。

然後在刪除key,重新加鎖的時候,校驗這個value是否為第一個執行緒的,匹配正確才刪除key,這是一種方案,當然並不是很好的解決方案,只能說是相對安全的,因為在高併發情況下面,執行緒的呼叫機制還是可以支援另外的執行緒持有鎖的。

4.2 叢集環境

下面介紹一下叢集環境的鎖問題,業務場景,假如一個執行緒在主伺服器(master)釋放鎖的時候,master突然冗機了,也是就是說鎖還沒被釋放。這時叢集環境檢測到master機器冗機,就切換從伺服器(slave)作為主伺服器,這時候,另外一個執行緒進來了,佔有了同一個鎖,也就是出現了兩個執行緒同時佔有同一個鎖的情況了。通過keepalive和redis實現主從伺服器自動failover的方式或許可以解決問題。因為並沒有實踐過,所以不做詳細解釋。這篇部落格
Redis自從自動failover或許可以參考。

ReadLock演算法
叢集環境的鎖同步是一個難題。上面的僅僅是我的想法並沒有實踐過,最近找到一個演算法可以解決,ReadLock演算法。readlock-py庫已經有對改演算法進行實踐。ReadLock演算法簡單原理就是通過先檢測set是否成功,set成功之後才向所有節點發送指令,釋放鎖。本部落格並不對ReadLock演算法做詳細介紹,有機會再寫部落格介紹。