分散式鎖 (資源在程式而非執行緒之間共享)
大多數網際網路系統都是分散式部署的,分散式部署確實能帶來效能和效率上的提升,但為此,我們就需要多解決一個分散式環境下,資料一致性的問題。
當某個資源在多系統之間,具有共享性的時候,為了保證大家訪問這個資源資料是一致的,那麼就必須要求在同一時刻只能被一個客戶端處理,不能併發的執行,否者就會出現同一時刻有人寫有人讀,大家訪問到的資料就不一致了。
一、我們為什麼需要分散式鎖?
在單機時代,雖然不需要分散式鎖,但也面臨過類似的問題,只不過在單機的情況下,如果有多個執行緒要同時訪問某個共享資源的時候,我們可以採用執行緒間加鎖的機制,即當某個執行緒獲取到這個資源後,就立即對這個資源進行加鎖,當使用完資源之後,再解鎖,其它執行緒就可以接著使用了。例如,在JAVA中,甚至專門提供了一些處理鎖機制的一些API(synchronize/Lock等)。
但是到了分散式系統的時代,這種執行緒之間的鎖機制,就沒作用了,系統可能會有多份並且部署在不同的機器上,這些資源已經不是線上程之間共享了,而是屬於程式之間共享的資源。
因此,為了解決這個問題,我們就必須引入「分散式鎖」。
分散式鎖,是指在分散式的部署環境下,通過鎖機制來讓多客戶端互斥的對共享資源進行訪問。
分散式鎖要滿足哪些要求呢?
- 排他性:在同一時間只會有一個客戶端能獲取到鎖,其它客戶端無法同時獲取
- 避免死鎖:這把鎖在一段有限的時間之後,一定會被釋放(正常釋放或異常釋放)
- 高可用:獲取或釋放鎖的機制必須高可用且效能佳
二、分散式鎖的實現方式有哪些?
分散式鎖中常見的三種機制,從實現的複雜度上來看,從上往下難度依次增加:
- 基於資料庫實現
- 基於Redis實現
- 基於ZooKeeper實現
無論哪種方式,其實都不完美,依舊要根據我們們業務的實際場景來選擇。
1. 基於資料庫來做分散式鎖
- 基於資料庫的樂觀鎖
- 基於資料庫的悲觀鎖
樂觀鎖機制其實就是在資料庫表中引入一個版本號(version)欄位來實現的。
當我們要從資料庫中讀取資料的時候,同時把這個version欄位也讀出來,如果要對讀出來的資料進行更新後寫回資料庫,則需要將version加1,同時將新的資料與新的version更新到資料表中,且必須在更新的時候同時檢查目前資料庫裡version值是不是之前的那個version,如果是,則正常更新。如果不是,則更新失敗,說明在這個過程中有其它的程式去更新過資料了。
如圖,假設同一個賬戶,使用者A和使用者B都要去進行取款操作,賬戶的原始餘額是2000,使用者A要去取1500,使用者B要去取1000,如果沒有鎖機制的話,在併發的情況下,可能會出現餘額同時被扣1500和1000,導致最終餘額的不正確甚至是負數。但如果這裡用到樂觀鎖機制,當兩個使用者去資料庫中讀取餘額的時候,除了讀取到2000餘額以外,還讀取了當前的版本號version=1,等使用者A或使用者B去修改資料庫餘額的時候,無論誰先操作,都會將版本號加1,即version=2,那麼另外一個使用者去更新的時候就發現版本號不對,已經變成2了,不是當初讀出來時候的1,那麼本次更新失敗,就得重新去讀取最新的資料庫餘額。
使用「樂觀鎖」機制,必須得滿足:
(1)鎖服務要有遞增的版本號version
(2)每次更新資料的時候都必須先判斷版本號對不對,然後再寫入新的版本號
悲觀鎖也叫作排它鎖,在Mysql中是基於 for update 來實現加鎖的
指對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。
悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。mysql(for update)悲觀鎖總結與實踐
//鎖定的方法-虛擬碼
public boolean lock(){
connection.setAutoCommit(false)//關閉自動提交屬性
for(){
result =select * from user where id = 100 for update;
if(result){
//結果不為空,則說明獲取到了鎖
//進行相應操作
return true;
}
//沒有獲取到鎖,繼續獲取
sleep(1000);
}
return false;
}
//釋放鎖-虛擬碼
connection.commit();//提交事務
上面的示例中,user表中,id是主鍵,通過 for update 操作,資料庫在查詢的時候就會給這條記錄加上排它鎖。
(需要注意的是MySQL InnoDB預設Row-Level Lock,只有欄位【明確指定主鍵】或加了索引的,才會是行級鎖 Row lock,否則是表級鎖 Table Lock 所以這個id欄位要加索引);
- 明確指定主鍵,並且有此資料,row lock
- 明確指定主鍵,若查無此資料,無lock
- 無主鍵,table lock
- 主鍵不明確,table lock
當這條記錄加上排它鎖之後,其它執行緒是無法操作這條記錄的。
那麼,這樣的話,我們就可以認為獲得了排它鎖的這個執行緒是擁有了分散式鎖,然後就可以執行我們想要做的業務邏輯,當邏輯完成之後,再呼叫上述釋放鎖的語句即可。
注意:在事務中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一筆資料時會等待其它事務結束後才執行,一般SELECT … 則不受此影響。例如當我執行select status from t_goods where id=1 for update;後。我在另外的事務中如果再次執行select status from t_goods where id=1 for update;則第二個事務會一直等待第一個事務的提交,此時第二個查詢處於阻塞的狀態,但是如果我是在第二個事務中執行select status from t_goods where id=1;則能正常查詢出資料,不會受第一個事務的影響。
2. 基於Redis實現
基於Redis實現的鎖機制,主要是依賴redis自身的原子操作,例如:
SET user_key user_value NX PX 100
redis從2.6.12版本開始,SET命令才支援這些引數:
NX:只在在鍵不存在時,才對鍵進行設定操作,SET key value NX 效果等同於 SETNX key value
PX millisecond:設定鍵的過期時間為millisecond毫秒,當超過這個時間後,設定的鍵會自動失效
上述程式碼示例是指,
當redis中不存在user_key這個鍵的時候,才會去設定一個user_key鍵,並且給這個鍵的值設定為 user_value,且這個鍵的存活時間為100ms
為什麼這個命令可以幫我們實現鎖機制呢?
因為這個命令是隻有在某個key不存在的時候,才會執行成功。那麼當多個程式同時併發的去設定同一個key的時候,就永遠只會有一個程式成功。
當某個程式設定成功之後,就可以去執行業務邏輯了,等業務邏輯執行完畢之後,再去進行解鎖。
解鎖很簡單,只需要刪除這個key就可以了,不過刪除之前需要判斷,這個key對應的value是當初自己設定的那個。
另外,針對redis叢集模式的分散式鎖,可以採用redis的Redlock機制。
3. 基於ZooKeeper實現
使用ZooKeeper的臨時有序節點來實現的分散式鎖。
原理就是:當某客戶端要進行邏輯的加鎖時,就在zookeeper上的某個指定節點的目錄下,去生成一個唯一的臨時有序節點, 然後判斷自己是否是這些有序節點中序號最小的一個,如果是,則算是獲取了鎖。如果不是,則說明沒有獲取到鎖,那麼就需要在序列中找到比自己小的那個節點,並對其呼叫exist()方法,對其註冊事件監聽,當監聽到這個節點被刪除了,那就再去判斷一次自己當初建立的節點是否變成了序列中最小的。如果是,則獲取鎖,如果不是,則重複上述步驟。
當釋放鎖的時候,只需將這個臨時節點刪除即可。
如圖,locker是一個持久節點,node_1/node_2/…/node_n 就是上面說的臨時節點,由客戶端client去建立的。
client_1/client_2/…/clien_n 都是想去獲取鎖的客戶端。以client_1為例,它想去獲取分散式鎖,則需要跑到locker下面去建立臨時節點(假如是node_1)建立完畢後,看一下自己的節點序號是否是locker下面最小的,如果是,則獲取了鎖。如果不是,則去找到比自己小的那個節點(假如是node_2),找到後,就監聽node_2,直到node_2被刪除,那麼就開始再次判斷自己的node_1是不是序列中最小的,如果是,則獲取鎖,如果還不是,則繼續找一下一個節點。
相關文章
- 使用redis分散式鎖解決併發執行緒資源共享問題Redis分散式執行緒
- 【計算機內功心法】十:執行緒間到底共享了哪些程式資源計算機執行緒
- 多執行緒之共享模型執行緒模型
- threading多執行緒資源加鎖thread執行緒
- python之執行緒鎖Python執行緒
- Java執行緒之鎖研究Java執行緒
- GCD 之執行緒死鎖GC執行緒
- Linux之執行緒互斥鎖Linux執行緒
- JAVA多執行緒共享資料Java執行緒
- Java多執行緒/併發05、synchronized應用例項:執行緒間操作共享資料Java執行緒synchronized
- 執行緒同步及執行緒鎖執行緒
- 多執行緒之間通訊及執行緒池執行緒
- 分散式之抉擇分散式鎖分散式
- 程式和執行緒有什麼區別?(Process and Threads)程式之間和執行緒之間是如何通訊的?執行緒thread
- 多執行緒之8鎖問題執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- Java 多執行緒共享模型之管程(上)Java執行緒模型
- 畫江湖之 PHP 多執行緒開發 【執行緒安全 互斥鎖】PHP執行緒
- 畫江湖之 PHP 多執行緒開發 [執行緒安全 互斥鎖]PHP執行緒
- [原始碼分析] 分散式任務佇列 Celery 多執行緒模型 之 子程式原始碼分散式佇列執行緒模型
- IO多路複用和多執行緒會影響Redis分散式鎖嗎?執行緒Redis分散式
- 多執行緒_鎖執行緒
- 執行緒鎖(四)執行緒
- Redis分散式鎖(二):鎖超時後導致多個執行緒獲得鎖的解決方案Redis分散式執行緒
- [譯] 在 Laravel 應用程式之間共享資料庫Laravel資料庫
- Java執行緒:執行緒的同步與鎖Java執行緒
- Java 多執行緒之內建鎖與顯示鎖Java執行緒
- Java 共享資料讀寫(多執行緒)Java執行緒
- 在共享記憶體中進行執行緒間的同步是確保多執行緒程式正確執行的關鍵,以下是幾種常見的方法記憶體執行緒
- python多執行緒程式設計3: 使用互斥鎖同步執行緒Python執行緒程式設計
- Java鎖?分散式鎖?樂觀鎖?行鎖?Java分散式
- 執行緒和鎖,鎖升級執行緒
- iOS多執行緒安全-13種執行緒鎖?iOS執行緒
- 多執行緒(2)-執行緒同步互斥鎖Mutex執行緒Mutex
- 手撕Java多執行緒(四)執行緒之間的協作Java執行緒
- JUC之執行緒間的通訊執行緒
- 多執行緒之間的通訊執行緒
- 多執行緒之間的競爭執行緒