一.分布式鎖要解決的問題
可以保證在分布式部署的應用集群中,同一個方法在同一時間只能被一台機器上的一個線程執行。
這把鎖要是一把可重入鎖(避免死鎖)
這把鎖最好是一把阻塞鎖(根據業務需求考慮要不要這條)
這把鎖最好是一把公平鎖(根據業務需求考慮要不要這條)
有高可用的獲取鎖和釋放鎖功能
獲取鎖和釋放鎖的性能要好
二.基於數據庫實現原理
1.新建鎖表記錄
CREATE TABLE `methodLock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法名', `desc` varchar(1024) NOT NULL DEFAULT '備注信息', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存數據時間,自動生成', PRIMARY KEY (`id`), UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法'; |
2.第一種實現
當想要鎖住某個方法時執行insert方法,插入一條數據,method_name有唯一約束,可以保證多次提交只有一次成功,而成功的這次就可以認為其獲得了鎖,而執行完成后執行delete語句釋放鎖
缺點:
這把鎖強依賴數據庫的可用性,數據庫是一個單點,一旦數據庫掛掉,會導致業務系統不可用。
這把鎖沒有失效時間,一旦解鎖操作失敗,就會導致鎖記錄一直在數據庫中,其他線程無法再獲得到鎖。
這把鎖只能是非阻塞的,因為數據的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的線程並不會進入排隊隊列,要想再次獲得鎖就要再次觸發獲得鎖操作。
這把鎖是非重入的,同一個線程在沒有釋放鎖之前無法再次獲得該鎖。因為數據中數據已經存在了。
這把鎖是非公平鎖,所有等待鎖的線程憑運氣去爭奪鎖。
3.第二種實現
還是使用上方的表結構,可以通過數據庫的排他鎖來實現分布式鎖
在查詢語句后面增加for update
,數據庫會在查詢過程中給數據庫表增加排他鎖。當某條記錄被加上排他鎖之后,其他線程無法再在該行記錄上增加排他鎖。
我們可以認為獲得排它鎖的線程即可獲得分布式鎖,當獲取到鎖之后,可以執行方法的業務邏輯,執行完方法之后,再通過connection.commit();
操作來釋放鎖
代碼:
-
public boolean lock(){
-
connection.setAutoCommit(false)
-
while(true){
-
try{
-
result = select * from methodLock where method_name=xxx for update;
-
if(result==null){
-
return true;
-
}
-
}catch(Exception e){
-
-
}
-
sleep(1000);
-
}
-
return false;
}-
public void unlock(){
-
connection.commit();
-
}
-
4.樂觀鎖實現
一般是通過為數據庫表添加一個 “version”字段來實現讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加1,在更新過程中,會對版本號進行比較,如果是一致的,沒有發生改變,則會成功執行本次操作;如果版本號不一致,則會更新失敗,實際就是個diff過程
缺點:
(1). 這種操作方式,使原本一次的update操作,必須變為2次操作: select版本號一次;update一次。增加了數據庫操作的次數。
(2). 如果業務場景中的一次業務流程中,多個資源都需要用保證數據一致性,那么如果全部使用基於數據庫資源表的樂觀鎖,就要讓每個資源都有一張資源表,這個在實際使用場景中肯定是無法滿足的。而且這些都基於數據庫操作,在高並發的要求下,對數據庫連接的開銷一定是無法忍受的。
(3). 樂觀鎖機制往往基於系統中的數據存儲邏輯,因此可能會造成臟數據被更新到數據庫中。
總結
數據庫鎖現在使用較多的就上面說的3種方式,排他鎖(悲觀鎖),版本號(樂觀鎖),記錄鎖,各有優缺點
數據庫鎖的優點就是 直接借助DB簡單易懂
缺點也很明顯:
會有各種各樣的問題,在解決問題的過程中會使整個方案變得越來越復雜。
操作數據庫需要一定的開銷,性能問題需要考慮