鎖和分布式鎖

2021-08-11 07:10:16 字數 2885 閱讀 1455

一 鎖和分布式鎖

1.1 鎖

我們一般所說的鎖,就是指單程序多執行緒的鎖機制。在單程序中,如果有多個執行緒併發訪問某個某個全域性資源,存在併發修改的問題。如果要避免這個問題,我們需要對資源進行同步,同步其實就是可以加乙個鎖來保證同一時刻只有乙個執行緒能操作這個資源。

在不同的場景,可能使用的鎖得方式不一樣,有的用的是synchronized關鍵字實現,有的使用可重入鎖reentrantlock,有的是基於volatile來實現。但追究根本,都是要滿足所有的執行緒都能知道這個鎖的存在,否則這個鎖就沒有任何意義。而且這個時候的鎖都是在記憶體中的。

1.2 分布式鎖

涉及到分布式環境,以集群為例,就是多個例項,也就是多個程序,而且這些程序完全可能不在同乙個機器上。我們知道多執行緒可以共享父程序的資源,包括記憶體。所以多執行緒可以看見鎖,但是多程序之間無法共享資源,甚至都不在一台機器上,所以這時候分布式環境下,就需要其他的方式來讓所有程序都可以知道這個鎖,來控制對全域性資源的併發修改。

為了解決分布式的問題,我們可以把這個鎖放入所有程序都可以訪問的地方,比如資料庫,redis,memcached或者是zookeeper。這些也是目前實現分布式鎖的主要實現方式。

二 分布式鎖設計要點

2.1 死鎖的問題

如果當加鎖成功,需要執行的**還沒有執行完畢,獲取鎖的節點忽然down機,那麼這時候無法釋放鎖,會造成其他節點上的執行緒也無法獲取鎖。

2.2 效能問題

設計的鎖不能成為效能瓶頸或者

2.3 單點問題

涉及到鎖不能有單點問題,如果有單點問題,那麼都獲取不到鎖,導致業務異常。

2.4 考慮鎖的可重入性

如有必要的業務場景,也需要考慮鎖的可重入性,什麼是可重入鎖?

即同乙個執行緒再次進入同步**塊的時候,可以使用自己已經獲取的鎖。如果獲取不到,這裡就是死鎖了。

2.5 釋放鎖之後,通知其他等待鎖的節點

如果不通知,處於等待狀態的鎖,或許只能根據乙個預定的時間再次申請鎖

三 基於資料庫表實現分布式鎖

我們可以將我們分布式要操作的資源都定義成表,表結構定義t_lock如下:

id:

resource: 資源

status: 狀態 0|1

add_time: 新增時間

update_time: 更新時間

version: 如果採用樂觀鎖,使用版本號,對當前資源的狀態進行更新就加1

大致流程就是:

select id, resource, status,version fromt_lock  where status=0 and id=***x;

如果查到了說明沒有資料,可以進行update

update t_lock set status=1, version=1,update_time=now() where id=***x and status=0 and version=0

否則,說明該鎖被其他執行緒持有,還沒有釋放

缺點:

更新之前會多一次查詢,增加了資料庫的操作

資料庫鏈結資源寶貴,如果併發量太大,資料庫的效能有影響

如果單個資料庫存在單點問題,所以最好是高可用的。

四 基於redis實現分布式鎖

4.1 基於redis實現的分布式的基本實現

通過redis的setnx key命令,如果不存在某個key就設定值,設定成功表示獲取鎖。

缺點:如果設定成功後,還沒有釋放鎖,對應的業務節點就掛掉了,那麼這時候鎖就沒有釋放。其他業務節點也無法獲取這個鎖。

4.2 基於redis實現的分布式的優化實現

在使用setnx設定命令成功後,則使用expire命令設定到期時間,就算業務節點還沒有釋放鎖就掛掉了,但是我們還是可以保證這個鎖到期就會釋放。

缺點:# setnx 和 expire不是原子操作,即設定了setnx還沒有來得及設定到期時間,業務節點就掛了。

# 而且key到期了,業務節點業務還沒有執行完,怎麼辦?

4.3 基於redis實現的分布式的再優化實現

4.3.1 使用set命令 我們知道set命令格式如下:

set key value [ex seconds] [px milliseconds][nx|xx]

即首先可以根據這個key不存在,則設定值,即使用nx。然後可以設定到期時間,ex表示秒數,px表示毫秒數,這個操作就是原子性的,解決了上述問題。

缺點:鎖到期了,業務節點業務還沒有執行完,怎麼辦?

4.3.2 基於redis實現的分布式的lua實現

通過lua指令碼,將setnx和expire變成乙個原子操作。

五 基於zookeeper實現分布式鎖

排它鎖:指的是乙個執行緒獨佔一把鎖,又稱寫鎖或者獨佔鎖。在zookeeper是通過資料節點來表示乙個鎖,比如/exclusive_lock/lock節點就表示乙個鎖

獲取鎖:

在需要獲取排它鎖的時候,所有客戶端都試圖通過呼叫create介面在對應的節點下(假設節點/exclusive_lock)建立臨時子節點,假設是(/exclusive_lock/lock)。zookeeper會保證在所有的客戶端中只有乙個客戶端能夠建立成功,那麼就可以認為該客戶端獲取了鎖。

同時沒有獲取鎖的客戶端需要在/exclusive_lock節點上註冊乙個監聽子節點變化的watcher,以便實時監聽到lock節點的變化。

釋放鎖:

# 當前獲取鎖的客戶端發生宕機,那麼zookeeper這個臨時節點就會被移除

# 正常業務邏輯執行完畢,客戶端就會主動將自己建立的節點刪除

缺點:所有獲取鎖失敗的客戶端都會在父節點上建立對子節點的監聽,當持有鎖的客戶端釋放鎖之後,所有等待的程序一起來建立節點,併發量很大。

優化方案:上鎖的時候改為建立臨時的有序節點,即ephemeral_sequential,判斷建立的節點序號是否最小,如果是最小則獲取鎖成功。不是則取鎖失敗,然後watch序號比本身小的前乙個節點。

分布式 分布式鎖

本質是利用redis的setnx 方法的特性來加鎖,setnx 即key不存在則設定key,否則直接返回false,要求在分布式系統中使用同乙個redis服務,以下提供兩種解決方案 1 直接使用redistemplate 這其實並不能完全保證高併發下的安全問題,因為可能在鎖過期之後該執行緒尚未執行完...

分布式鎖 紅鎖

單台redis支援 set key value nx px time value 必須唯一避免誤解鎖,設定與失效時間不能分割,刪除鎖判斷是否解鎖人就是加鎖人,鎖失效時間應自動延長有效期 1 獲取當前時間 2 向多個節點獲取鎖,獲取鎖有乙個極小的超時時間。獲取鎖失敗後,立即嘗試下乙個redis節點獲取...

redis 單機鎖 和 分布式鎖

偷自 require vendor autoload.php client new predis client scheme tcp host 127.0.0.1 port 6379,class redislock desc 獲取鎖鍵名 public function getlockcachekey...