分佈式鎖MySQL實現

分佈式系統中,一個避不開的話題,就是在很多情況下,我們需要用到分佈式鎖。
那分佈式鎖,通常有哪幾種實現方式呢?
分佈式鎖的實現方式,通常有三種,數據庫實現,Redis實現,Zookeeper實現。
我們將分三篇文章來分別介紹這三種實現。
首先要來介紹的是數據庫版實現的分佈式鎖。我們看以下幾個場景中,如何用mysql數據庫來實現一個分佈式鎖。

場景1:我們曾經做的一個貸款系統,在用戶借款前,需要給用戶開一個還款的賬戶。還款賬戶一個用戶只需要開一個就夠了,但線上使用時,偶爾會出現一個用戶開了多個還款賬戶的情況,原因就是一個客戶同時借了幾筆款,出現了併發。
出現併發的代碼邏輯大概是這樣的:
step1:查詢用戶A是否開過還款賬戶。
select * from acct_info where userid = ‘用戶A’。
step2:如果step1查到用戶A已開戶,則return,不再開戶。
如果step2查到用戶A未開戶,則去調用開戶接口開戶。
當step1執行查詢時,剛好有兩個線程併發,線程A,線程B同時去查詢是否開過戶,這時線程A,線程B查詢到的結果都是未開戶,都去執行開戶邏輯,就會開出兩個戶,這是我們不希望看到的。
那針對場景1的這種情況,我們如何用mysql通過實現一個分佈式鎖來解決呢?
這種場景,我們可以用mysql的唯一索引來解決。
我們可以建一張acct_control表,裏面有一個userid字段,並給該字段建一個唯一索引。在開戶前,我們先往acct_control表中插入一條數據,insert into acct_control (userid) values (‘用戶A’),如果插入成功,就認爲獲取到了鎖,然後可以去執行開戶邏輯。如果出現場景1中併發的情況,有兩個線程同時往acct_control表中寫數據,因爲我們在userid上建了一個唯一索引,那麼必然只會有一個線程寫入成功,一個線程寫入失敗。
寫入成功的我們認爲是獲取到了鎖,可以去執行開戶流程,寫入失敗的,我們認爲沒有獲取鎖,不能去開戶。
場景2:我曾經做過的一個銀行系統中有預約轉賬的功能,比如現在是5.1號,客戶預約一個禮拜後的5.7號進行把平安銀行賬戶上的錢轉到農業銀行去還房貸。5.1號客戶操作後,我們會先記錄一個預約流水,到了5.7號會通過一個定時任務,把客戶的預約流水撈出來進行轉賬操作。這裏需要用到任務調度功能,在分別式系統中,我們爲了保證系統的高可用,通常在多臺實例上,都會部署調度任務。但預約轉賬的job真正只能被一臺實例去執行,這裏也涉及到分佈式鎖的實現。我們看下,這種情況下,我們如何用mysql來實現一個分佈式鎖。
我們可以建一張,定時任務加鎖表,task_lock_table。在該表中有如下幾個字段。
task_lock_table(
task_id varchar(50) primary key, --task_id
task_name varchar(100), --task名稱
lock_status varchar(10), --鎖狀態 LOCK(已被鎖), UNLOCK(未被鎖)
version varchar(10), --當前鎖版本號
modify_time varchar(20) --修改時間
)
我們給這張表進行初始化一下:
insert into task_lock_table values
(‘appoint_transfer’,‘預約轉賬’,‘UNLOCK’,‘0’,‘1977-12-31 00:00:00’)
假設我們的job服務器共有三臺,這三臺服務器同時啓動,要去執行預約轉賬的任務,
我們只能讓其中的一臺去真正的執行轉賬的服務。我們怎麼控制呢?
step1:先獲取鎖,查詢task_lock_table,看下lock_status狀態。
A:如果lock_status狀態爲UNLOCK,則認爲鎖未被佔用,去獲取該鎖,同時也需要獲取當前的version,假如version=‘0’。
update task_lock_table set lock_status = ‘LOCK’, modify_time = now(), version=version+1 where task_id = ‘appoint_transfer’ and version=‘0’,
如果update成功,則成功獲取該鎖。可以去執行對應的轉賬的操作。
如果update沒有更新成功,則認爲沒有獲取到鎖,不能執行轉賬操作。
B:如果lock_status狀態爲LOCK,則認爲鎖已被佔用。這時又分兩種情況:
b1: 如果modify_time和當前時間差小於超時時間,則認爲該鎖是被佔用,獲取鎖失敗,不執行轉賬任務。
b2:如果modify_time和當前時間差大於超時時間,則認爲該鎖未被佔用(鎖已被釋放),去獲取該鎖,查詢獲取當前version=‘1’。
update task_lock_table set lock_status = ‘LOCK’, modify_time = now(), version=version+1 where task_id = ‘appoint_transfer’ and version=‘1’。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章