基於資料庫的分布式鎖實現

2021-08-10 14:51:31 字數 3247 閱讀 4394

一、基於資料庫表

要實現分布式鎖,最簡單的方式可能就是直接建立一張鎖表,然後通過操作該表中的資料來實現了。當我們要鎖住某個方法或資源的時候,我們就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄。

建立這樣一張資料庫表:

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

notnull

default

current_timestamp

onupdate

current_timestamp comment '儲存資料時間,自動生成',

primary

key (`id`),

unique

key`uidx_method_name` (`method_name `) using btree

) engine=innodb default charset=utf8 comment='鎖定中的方法';

當我們要鎖住某個方法時,執行以下sql:

insert

into methodlock(method_name,desc) values (『method_name』,『desc』)

因為我們對method_name做了唯一性約束,這裡如果有多個請求同時提交到資料庫的話,資料庫會保證只有乙個操作可以成功,那麼我們可以認為操作成功的那個執行緒獲得了該方法的鎖,可以執行具體內容。

當方法執行完畢之後,想要釋放鎖的話,需要執行以下sql:

delete

from methodlock where method_name ='method_name'

上面這種簡單的實現有以下幾個問題:

1、這把鎖依賴資料庫的可用性,資料庫是乙個單點,一旦資料庫掛掉,會導致業務系統不可用。

2、這把鎖沒有失效時間,一旦解決操作失敗,就會導致記錄一直在資料庫中,其他執行緒無法在獲得鎖。

3、這把鎖只能是非阻塞的,因為資料的insert操作,一旦插入失敗就會直接報錯。沒有獲得鎖的執行緒並不會進入排隊佇列,要想再次獲得鎖就要再次觸發獲得鎖的操作。

4、這把鎖是非重入的,同乙個執行緒在沒有釋放鎖之前無法再次獲得該鎖。因為資料庫表中資料已經存在了。

當然,我們也可以有其它方式解決上面的問題:

1、資料庫是單點?那就搞兩個資料庫,資料庫之前雙向同步,一旦掛掉快速切換到備庫上。

2、沒有失效時間?可以做乙個定時任務,每隔一定時間把資料庫中的超時資料清理一遍。

3、非阻塞?可以寫乙個while迴圈,直到insert成功再返回成功。

4、非重入?可以在資料庫表中加乙個字段,記錄當前獲得鎖的機器的主機資訊和執行緒資訊,那麼下次再獲取鎖的時候先查詢資料庫,如果當前機器的主機資訊和執行緒資訊在資料庫中可以查到的話,就直接把鎖分配給它即可。

二、基於資料庫表做樂觀鎖

常用的方法是為資料增加乙個版本標識,具體實現請參考

三、基於資料庫表做悲觀鎖(排它鎖)

除了可以通過增刪運算元據庫表中的記錄以外,還可以借助資料庫中自帶的鎖來實現分布式鎖。

我們還用上面建立的資料庫表,可以通過資料庫的排它鎖來實現分布式鎖。基於mysql的innodb引擎,可以使用以下方法來實現加鎖操作

public boolean lock()

} catch (exception e)

sleep(1000);

}returntype false;

}

在查詢語句後面增加for update,資料庫會在查詢過程中給資料庫表增加排他鎖(這裡再多提一句,innodb引擎在加鎖的時候,只有通過索引進行檢索的時候才會使用行級鎖,否則會使用表級鎖。這裡我們希望使用行級鎖,就要給method_name新增索引,值得注意的是,這個索引一定要建立成唯一索引,否則會出現多個過載方法之間無法同時被訪問的問題。過載方法的話建議把引數型別也加上)。當某條記錄被加上排他鎖之後,其他執行緒無法再在該行記錄上增加排他鎖。

我們可以認為獲得排它鎖的執行緒即可獲得分布式鎖,當獲取到鎖之後,可以執行方法的業務邏輯,執行完方法之後,再通過以下方法解鎖:

public

void

unlock()

通過connection.commit()操作來釋放鎖。

這裡還可能存在另外乙個問題,雖然我們對method_name 使用了唯一索引,並且顯示使用for update來使用行級鎖。但是,mysql會對查詢進行優化,即便在條件中使用了索引字段,但是否使用索引來檢索資料是由 mysql 通過判斷不同執行計畫的代價來決定的,如果 mysql 認為全表掃效率更高,比如對一些很小的表,它就不會使用索引,這種情況下 innodb 將使用表鎖,而不是行鎖。如果發生這種情況就悲劇了。

還有乙個問題,就是我們要使用排他鎖來進行分布式鎖的lock,那麼乙個排他鎖長時間不提交,就會占用資料庫連線。一旦類似的連線變得多了,就可能把資料庫連線池撐爆。

這種方法可以有效的解決上面提到的無法釋放鎖和阻塞鎖的問題

1、阻塞鎖?for update語句會在執行成功後立即返回,在執行失敗時一直處於阻塞狀態,直到成功。

2、鎖定之後服務宕機,無法釋放?使用這種方式,服務宕機之後資料庫會自己把鎖釋放掉。

但是還是無法解決資料庫單點和可重入的問題

優點:直接借助資料庫容易理解;

缺點:

1、會有各種各樣的問題,在解決問題的過程中會使整個方案變的越來越複雜。

2、運算元據庫需要一定的開銷,效能問題需要考慮。

總結:

使用資料庫來實現分布式鎖,這三種方式都是依賴資料庫中的一張表,一種是通過表中的記錄存在情況確定當前是否有鎖存在,另外兩種是通過資料庫的樂觀鎖和排它鎖/悲觀鎖來實現分布式鎖

基於資料庫實現分布式鎖

多個程序 多個執行緒訪問共同元件資料庫.通過selec.for update訪問同一條資料 for update鎖定資料,其他執行緒只能等待 此時只有乙個操作可以對資料進行修改,而其他人不能夠對該資料進行修改操作,但可以檢視 select from distribute lock where bus...

基於資料庫的分布式鎖實現

一 基於資料庫表 要實現分布式鎖,最簡單的方式可能就是直接建立一張鎖表,然後通過操作該表中的資料來實現了。當要鎖住某個方法或資源的時候,就在該表中增加一條記錄,想要釋放鎖的時候就刪除這條記錄。建立這樣一張資料庫表 create table methodlock id int 11 not null ...

基於資料庫的分布式鎖

使用場景 某大型 部署是分布式的,訂單系統有三颱伺服器響應使用者請求,生成訂單後統一存放到order info表 order info表要求訂單id order id 必須是唯一的,那麼三颱伺服器怎麼協同工作來確認order id的唯一性呢?這時候就要用到分布式鎖了。分布式鎖的要求 在了解了使用場景...