溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在MySQL中利用DB實(shí)現(xiàn)分布式鎖

發(fā)布時(shí)間:2021-06-01 18:02:08 來(lái)源:億速云 閱讀:256 作者:Leah 欄目:MySQL數(shù)據(jù)庫(kù)

怎么在MySQL中利用DB實(shí)現(xiàn)分布式鎖?相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

表設(shè)計(jì)

首先要明確DB在系統(tǒng)中仍然需要認(rèn)為是最脆弱的一環(huán),因此在設(shè)計(jì)時(shí)需要考慮壓力問(wèn)題,即能應(yīng)用實(shí)現(xiàn)的邏輯就不要放到DB上實(shí)現(xiàn),也就是盡量少使用DB提供的鎖能力,如果是高并發(fā)業(yè)務(wù)則要避免使用DB鎖,換成Redis等緩存鎖更加有效。如清單1所示,該表中唯一的約束為lock_name,timestamp,version三者組合主鍵,下文會(huì)利用這三者實(shí)現(xiàn)悲觀鎖,樂(lè)觀鎖等業(yè)務(wù)場(chǎng)景。

清單1: 分布式鎖表結(jié)構(gòu)

CREATE TABLE `lock` (
`lock_name` varchar(32) NOT NULL DEFAULT '' COMMENT '鎖名稱',
`resource` bigint(20) NOT NULL COMMENT '業(yè)務(wù)主鍵',
`version` int(5) NOT NULL COMMENT '版本',
`gmt_create` datetime NOT NULL COMMENT '生成時(shí)間',
PRIMARY KEY (`lock_name`,`resource`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

悲觀鎖實(shí)現(xiàn)

對(duì)于悲觀鎖業(yè)務(wù)中常見(jiàn)的操作有以下兩種:

怎么在MySQL中利用DB實(shí)現(xiàn)分布式鎖

針對(duì)A:

A場(chǎng)景當(dāng)一臺(tái)機(jī)器獲取到鎖后,其他機(jī)器處于排隊(duì)狀態(tài),鎖釋放后其他機(jī)器才能夠繼續(xù)下去,這種應(yīng)用層面解決是相當(dāng)麻煩,因此一般使用DB提供的行鎖能力,即select xxx from xxx for update。A場(chǎng)景一般都和業(yè)務(wù)強(qiáng)關(guān)聯(lián),比如庫(kù)存增減,使用業(yè)務(wù)對(duì)象作為行鎖即可。需要注意的是,該方案本質(zhì)上鎖壓力還是在數(shù)據(jù)庫(kù)上,當(dāng)阻塞住的線程過(guò)多,且操作耗時(shí),最后會(huì)出現(xiàn)大量鎖超時(shí)現(xiàn)象。

針對(duì)B:

針對(duì)B場(chǎng)景(tryLock)舉個(gè)具體業(yè)務(wù),在集群下每臺(tái)機(jī)器都有定時(shí)任務(wù),但是業(yè)務(wù)上要求同一時(shí)刻只能有一臺(tái)能正常調(diào)度。
解決思路是利用唯一主鍵約束,插入一條針對(duì)TaskA的記錄,版本則默認(rèn)為1,插入成功的算獲取到鎖,繼續(xù)執(zhí)行業(yè)務(wù)操作。這種方案當(dāng)機(jī)器掛掉就會(huì)出現(xiàn)死鎖,因此還需要有一個(gè)定時(shí)任務(wù),定時(shí)清理已經(jīng)過(guò)期的鎖,清理維度可以根據(jù)lock_name設(shè)置不同時(shí)間清理策略。

定時(shí)任務(wù)清理策略會(huì)額外帶來(lái)復(fù)雜度,假設(shè)機(jī)器A獲取到了鎖,但由于CPU資源緊張,導(dǎo)致處理變慢,此時(shí)鎖被定時(shí)任務(wù)釋放,因此機(jī)器B也會(huì)獲取到鎖,那么此時(shí)就出現(xiàn)同一時(shí)刻兩臺(tái)機(jī)器同時(shí)持有鎖的現(xiàn)象,解決思路:把超時(shí)時(shí)間設(shè)置為遠(yuǎn)大于業(yè)務(wù)處理時(shí)間,或者增加版本機(jī)制改成樂(lè)觀鎖。

insert into lock set lock_name='TaskA' , resource='鎖住的業(yè)務(wù)',version=1,gmt_create=now()
success: 獲取到鎖
failed:放棄操作
釋放鎖

樂(lè)觀鎖實(shí)現(xiàn)

針對(duì)樂(lè)觀鎖場(chǎng)景,舉個(gè)具體業(yè)務(wù),在后臺(tái)系統(tǒng)中經(jīng)常使用大json擴(kuò)展字段存儲(chǔ)業(yè)務(wù)屬性,在涉及部分更新時(shí),需要先查詢出來(lái),合并數(shù)據(jù),寫(xiě)入到DB,這個(gè)過(guò)程中如果存在并發(fā),則很容易造成數(shù)據(jù)丟失,因此需要使用鎖來(lái)保證數(shù)據(jù)一致性,相應(yīng)操作如下所示,針對(duì)樂(lè)觀鎖,不存在死鎖,因此這里直接存放業(yè)務(wù)id字段,保證每一個(gè)業(yè)務(wù)id有一條對(duì)應(yīng)的記錄,并且不需要對(duì)應(yīng)的定時(shí)器清除。

select * from lock where lock_name='業(yè)務(wù)名稱', resource='業(yè)務(wù)id';
不存在: insert into lock set lock_name='業(yè)務(wù)名稱', resource='業(yè)務(wù)id' , version=1;
獲取版本: version
業(yè)務(wù)操作: 取數(shù)據(jù),合并數(shù)據(jù),寫(xiě)回?cái)?shù)據(jù)
寫(xiě)回到DB: update lock set version=version+1 where lock_name='業(yè)務(wù)名稱' and resource='業(yè)務(wù)id' and version= #{version};
寫(xiě)回成功: 操作成功
寫(xiě)回失敗: 回滾事務(wù),從頭操作

看完上述內(nèi)容,你們掌握怎么在MySQL中利用DB實(shí)現(xiàn)分布式鎖的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI