在分布式環境下經常會出現這樣的需求,多個服務器節點調用遠程服務器的某項資源,但是這樣的資源在同一時間點只允許一個服務器節點使用,類似於這樣機器與機器之間的並發無法通過傳統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.數據庫阻塞鎖的實現可能會造成占用大量數據庫連接池連接導致其它線程無連接可用
