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

2021-10-09 23:03:42 字數 4432 閱讀 3830

一、基於資料庫表

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

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

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'

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

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

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

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

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

針對以上幾點,因此有其他方式解決上面的問題:

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

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

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

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

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

系統認為資料的更新在大多數情況下是不會產生衝突的,只在資料庫更新操作提交的時候才對資料作衝突檢測。如果檢測的結果出現了與預期資料不一致的情況,則返回失敗資訊。

樂觀鎖大多數是基於資料版本(version)的記錄機制實現的。何謂資料版本號?即為資料增加乙個版本標識,在基於資料庫表的版本解決方案中,一般是通過為資料庫表新增乙個 「version」欄位來實現讀取出資料時,將此版本號一同讀出,之後更新時,對此版本號加1。在更新過程中,會對版本號進行比較,如果是一致的,沒有發生改變,則會成功執行本次操作;如果版本號不一致,則會更新失敗。

使用版本號實現樂觀鎖

使用版本號時,可以在資料初始化時指定乙個版本號,每次對資料的更新操作都對版本號執行+1操作。並判斷當前版本號是不是該資料的最新的版本號。查詢出商品資訊:

create

table

`optimistic_lock`

(`id`

bigint

notnull

auto_increment

,`resource`

int not

null

comment

'鎖定的資源'

,`version`

int not

null

comment

'版本資訊'

,`created_at`

datetime comment

'建立時間'

,`updated_at`

datetime comment

'更新時間'

,`deleted_at`

datetime comment

'刪除時間'

,primary

key(

`id`),

unique

key`uiq_idx_resource`

(`resource`))

engine

=innodb default

charset

=utf8mb4 comment

='資料庫分布式鎖表'

;

除此之外借助更新時間戳(updated_at)也可以實現樂觀鎖,和採用version欄位的方式相似:更新操作執行前線獲取記錄當前的更新時間,在提交更新時,檢測當前更新時間是否與更新開始時獲取的更新時間戳相等。

優點與不足

樂觀鎖的優點比較明顯,由於在檢測資料衝突時並不依賴資料庫本身的鎖機制,不會影響請求的效能,當產生併發且併發量較小的時候只有少部分請求會失敗。缺點是需要對錶的設計增加額外的字段,增加了資料庫的冗餘,另外,當應用併發量高的時候,version值在頻繁變化,則會導致大量請求失敗,影響系統的可用性。所以綜合資料庫樂觀鎖的優缺點,樂觀鎖比較適合併發量不高,並且寫操作不頻繁的場景。

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

除了可以通過增刪運算元據庫表中的記錄以外,還可以借助資料庫中自帶的鎖來實現分布式鎖。基於mysql的innodb引擎,可以使用以下方法來實現加鎖操作:

public boolean lock()

}catch

(exception e)

sleep

(1000);

} returntype false

;}

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

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

public

void

unlock()

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

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

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

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

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

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

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

基於資料庫實現分布式鎖

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

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

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

基於資料庫的分布式鎖

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