分散式鎖之資料庫鎖
在分散式環境下經常會出現這樣的需求,多個伺服器節點呼叫遠端伺服器的某項資源,但是這樣的資源在同一時間點只允許一個伺服器節點使用,類似於這樣機器與機器之間的併發無法通過傳統java併發API來解決.於是便有了分散式鎖
資料庫鎖是併發鎖的一種實現
分散式鎖需要滿足以下兩個條件
- 在分散式環境下,在同一時間只能被一臺機器的一個執行緒執行
- 為了避免死鎖,分散式鎖是一把可重入鎖
- 鎖的獲取和釋放必須高效能
CREATE TABLE `resource_lock` (
`id` int(11) NOT NULL AUTO_INCREMENT
`resource_name` varchar(64 ) NOT NULL ,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '儲存資料時間,自動生成',
`holder_info` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_resource` (`resource_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '資源鎖表';
鎖表的關鍵點在於唯一索引 UNIQUE KEY,這一條件保證了在任何時候每個資源只能有一個主機能夠獲取該資源.而holder_info記錄了獲取資源的是誰,這個欄位使分散式鎖支援可重入性,從而避免了死鎖
如果我們鎖住某個資源資源的時候,我們首先獲取資源,獲取資源的方法是在該表中新增一個資源,如果新增成功,則獲取資源成功,反之失敗
這個分散式鎖仍然存在如下問題:
1.資料庫必須是叢集狀態,若是單點資料庫,一旦資料庫癱瘓,整個系統都將處於不可用狀態
2.如果某臺主機獲取了該鎖,但是因為意外情況宕機,導致該資源一直處於佔用狀態.所以需要在應用層加上定時task,根據updateTime清除到時但是沒有被釋放的鎖
如果我們需要該鎖支援阻塞,也就是當節點沒有獲取到鎖的時候就等待,直到獲取鎖,我們在插入資料(也就是獲取鎖)失敗的情況下,使用mysql悲觀鎖鎖定該鎖,mysql程式碼如下
-- 獲取鎖程式碼如下
begin;
select * from resource_lock where resource_name='resource' for update
-- 業務邏輯
dosomething()
-- 釋放鎖程式碼如下
commit;
該方法有幾個注意點:
1.務必對resource_name新增索引,否則mysql將會鎖全表,這會造成其它執行緒都無法申請鎖
2.其它執行緒等待獲取鎖的時候阻塞時間是有限制的,超時後mysql會報如下的錯誤
insert into resource_lock values(1,'resource',current_timestamp,'holder_info');
lock wait time可以自己更改mysql配置
3.我們要使用排它鎖進行分散式鎖的lock,如果一個排他鎖不提交,會長時間佔用資料庫連線池連線,如果這樣的連線變多,可能會造成其它執行緒獲取mysql連線失敗
基於資料庫的分散式鎖優缺點分析
優點:
1.直接藉助資料庫容易理解
2.資料庫鎖具有持久化特性
缺點
1.操作資料庫會有效能開銷
2.資料庫阻塞鎖的實現可能會造成佔用大量資料庫連線池連線導致其它執行緒無連線可用