一、分散式鎖背景 a、什麼是鎖? 從使用場景定義:當存在多個線程可以同時改變某個變數時,就需要對變數或代碼塊做同步,使其在修改這種變數時能夠線性執行消除併發修改變數。 鎖的實現方式有多種,只要能滿足所有線程都能看得到這個鎖標記即可。 Java中常見的鎖: synchronized Reentrant ...
一、分散式鎖背景
a、什麼是鎖?
從使用場景定義:當存在多個線程可以同時改變某個變數時,就需要對變數或代碼塊做同步,使其在修改這種變數時能夠線性執行消除併發修改變數。
鎖的實現方式有多種,只要能滿足所有線程都能看得到這個鎖標記即可。
Java中常見的鎖:
synchronized
ReentrantLock
ReentrantReadWriteLock
b、什麼是分散式?
定義:分散式系統一定是由多個節點(電腦伺服器)組成的系統,這些互通的節點上部署了各個服務,並且操作需要協同。
分散式系統對於終端用戶而言,他們面對的就好像是一個伺服器,提供用戶需要的服務而已。
什麼是CAP?
定義:任何一個分散式系統都無法同時滿足一致性Consistency、可用性Availability、和分區容錯性Partition Tolerance,最多只能同時滿足兩項。
因為單個伺服器偶爾宕機不可避免(或者因為停電或者自然災害導致單機房不可用),網路狀況不穩定,時而會有網路抖動,時而延時比較高,所以必須滿足P,所以一般會出現AP系統和CP系統。
c、a+b==》什麼是分散式鎖?
定義:在分散式環境下,一個共用的可見的公共資源,各個線程通過對這個公共資源的搶占,能夠使得一個代碼塊在同一時間只能被一個機器的一個線程執行,那這個公共資源就是分散式鎖,或者說這整個機制就是分散式鎖。
或者從使用場景定義:分散式鎖主要用於在分散式環境中保護跨進程、跨主機、跨網路的共用資源實現互斥訪問,以達到保證數據的一致性
二、分散式鎖實現方式
鎖的實現方式有多種,只要能滿足所有線程都能看得到這個鎖標記即可。
常見的方式是使用資料庫、緩存或者zookeeper來實現分散式鎖,除了這些,其實一個網路中的共用可見的可讀寫的資源就可以用作實現鎖。
鎖的操作主要有兩個,即lock和unlock。
a、資料庫
我們可以利用資料庫的特性,lock即插入一條數據到資料庫,unlock即刪除該條數據。直接看圖
1、建mysql表,lock_name上建立唯一性索引;
2、server1開始獲取分散式鎖"aaa";
3、server1調用lock("aaa"),資料庫中沒有"aaa",插入"aaa"成功,server1成功獲得分散式鎖"aaa";
4、server2調用lock("aaa"),插入"aaa"失敗,獲取鎖失敗;
5、server1調用unlock("aaa"),刪除數據行"aaa";
6、server2調用lock("aaa"),插入"aaa"成功,獲取鎖成功;
熟悉java鎖的同學發現問題了,這個鎖也太簡陋了,鎖都不阻塞的。
好,我們嚴格地來討論下鎖的特性,
首先,我們羅列下鎖的特性,我能想到的有以下這些,歡迎留言補充:
分散式鎖的需求
1、在分散式環境下,一個代碼塊在同一時間只能被一個機器的一個線程執行
2、高可用地獲取鎖和釋放鎖
3、高性能的獲取鎖和釋放鎖
4、具備鎖失效機制,防止死鎖
5、是否可重入
6、是否非阻塞
7、自旋鎖
8、樂觀鎖/悲觀鎖
9、是否公平
10、獨享鎖/共用鎖
11、分段鎖
剛纔提到的問題,鎖最基本的應該具有阻塞特性,我們修正一下,如下圖
在插入數據失敗後,我們通過不斷地重試來達到阻塞的特性。
下麵我們來逐條討論下以上提到的特性:
分散式鎖的需求
1、在分散式環境下,一個代碼塊在同一時間只能被一個機器的一個線程執行
基本的,所有的鎖都應該滿足
2、高可用地獲取鎖和釋放鎖
可用性受資料庫單點限制,如果要提高可用性,使用主備庫,通過數據同步主備切換達到在主庫宕機時的高可用
3、高性能的獲取鎖和釋放鎖 資料庫讀寫性能
鎖的獲取和釋放就是資料庫的插入和刪除,性能因具體資料庫不同而不同,表的行數的規模大小也影響著性能,性能還受網路的影響,在筆者的網路中插入大概耗時約80ms
4、具備鎖失效機制,防止死鎖
如果節點在獲取鎖之後釋放鎖之前宕機,會發生死鎖。可以在表中加入時間欄位,用一個定時任務,定期地清理時間過期的鎖,但是這個過期時間的大小值得探討,很深入地探討。
5、是否可重入
如果想獲得類似ReentrantLock的特性,可以在表中寫入線程的信息(IP地址+進程號+線程號),可以先查詢,如果是線程自己擁有的鎖,直接返回成功並計數count
6、是否非阻塞
如果要獲得阻塞特性,通過不斷自旋重試
7、自旋鎖
同上
8、樂觀鎖/悲觀鎖
分散式鎖就是悲觀鎖。如果不想用分散式鎖,對數據不上鎖,在表中加入列version,通過CAS版本號來控制併發寫
9、是否公平
首先解釋下公平鎖:公平鎖即是根據lock()的時間順序,先到先得,即FIFO,這個是常見的規則。
單機情況下非公平鎖的好處:公平鎖忽略了線程之間切換以及線程內核態和用戶態轉換的耗時,其實可以利用線程切換的間隙,讓其它正在被調度執行的線程插隊去獲取鎖,然後釋放鎖,還給等待線程。這樣不耽誤事的前提下,提高了鎖的性能。請自行去瞭解下非公平鎖。
言歸正傳,如果需要實現公平鎖,設計一張排隊表,讓lock()的線程首先在排隊表中排隊,當發現自身排在隊頭才有資格去真正獲取鎖。當然引入複雜性,必然會導致其它問題,兵來將擋,水來土掩。
10、獨享鎖/共用鎖
以上的分散式鎖,是定義的一把獨享鎖(獨占鎖、互斥鎖、排它鎖)。
ReentrantLock就是一種排它鎖。CountDownLatch是一種共用鎖。這兩類都是單純的一類,即,要麼是排它鎖,要麼是共用鎖。
ReentrantReadWriteLock是同時包含排它鎖和共用鎖特性的一種鎖,這裡主要以ReentrantReadWriteLock為例來進行分析學習。
如果要實現共用鎖,鎖類型+排隊表
e、結論
分散式鎖可以根據業務需要,量身定製。
三、存在的問題