MySQL借助DB實現(xiàn)分布式鎖思路詳解
前言
無論是單機鎖還是分布式鎖,原理都是基于共享的數(shù)據(jù),判斷當前操作的行為。對于單機則是共享RAM內(nèi)存,對于集群則可以借助Redis,ZK,DB等第三方組件來實現(xiàn)。Redis,ZK對分布式鎖提供了很好的支持,基本上開箱即用,然而這些組件本身要高可用,系統(tǒng)也需要強依賴這些組件,額外增加了不少成本。DB對于系統(tǒng)來說本身就默認為高可用組件,針對一些低頻的業(yè)務使用DB實現(xiàn)分布式鎖也是一個不錯的解決方案,比如控制多機器下定時任務的起調(diào),針對審批回調(diào)處理等,本文將給出DB實現(xiàn)分布式鎖的一些場景以及解決方案,希望對你啟發(fā)。
表設(shè)計
首先要明確DB在系統(tǒng)中仍然需要認為是最脆弱的一環(huán),因此在設(shè)計時需要考慮壓力問題,即能應用實現(xiàn)的邏輯就不要放到DB上實現(xiàn),也就是盡量少使用DB提供的鎖能力,如果是高并發(fā)業(yè)務則要避免使用DB鎖,換成Redis等緩存鎖更加有效。如清單1所示,該表中唯一的約束為lock_name,timestamp,version三者組合主鍵,下文會利用這三者實現(xiàn)悲觀鎖,樂觀鎖等業(yè)務場景。
清單1: 分布式鎖表結(jié)構(gòu)
CREATE TABLE `lock` ( `lock_name` varchar(32) NOT NULL DEFAULT '' COMMENT '鎖名稱', `resource` bigint(20) NOT NULL COMMENT '業(yè)務主鍵', `version` int(5) NOT NULL COMMENT '版本', `gmt_create` datetime NOT NULL COMMENT '生成時間', PRIMARY KEY (`lock_name`,`resource`,`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
悲觀鎖實現(xiàn)
對于悲觀鎖業(yè)務中常見的操作有以下兩種:
針對A:
A場景當一臺機器獲取到鎖后,其他機器處于排隊狀態(tài),鎖釋放后其他機器才能夠繼續(xù)下去,這種應用層面解決是相當麻煩,因此一般使用DB提供的行鎖能力,即select xxx from xxx for update。A場景一般都和業(yè)務強關(guān)聯(lián),比如庫存增減,使用業(yè)務對象作為行鎖即可。需要注意的是,該方案本質(zhì)上鎖壓力還是在數(shù)據(jù)庫上,當阻塞住的線程過多,且操作耗時,最后會出現(xiàn)大量鎖超時現(xiàn)象。
針對B:
針對B場景(tryLock)舉個具體業(yè)務,在集群下每臺機器都有定時任務,但是業(yè)務上要求同一時刻只能有一臺能正常調(diào)度。
解決思路是利用唯一主鍵約束,插入一條針對TaskA的記錄,版本則默認為1,插入成功的算獲取到鎖,繼續(xù)執(zhí)行業(yè)務操作。這種方案當機器掛掉就會出現(xiàn)死鎖,因此還需要有一個定時任務,定時清理已經(jīng)過期的鎖,清理維度可以根據(jù)lock_name設(shè)置不同時間清理策略。
定時任務清理策略會額外帶來復雜度,假設(shè)機器A獲取到了鎖,但由于CPU資源緊張,導致處理變慢,此時鎖被定時任務釋放,因此機器B也會獲取到鎖,那么此時就出現(xiàn)同一時刻兩臺機器同時持有鎖的現(xiàn)象,解決思路:把超時時間設(shè)置為遠大于業(yè)務處理時間,或者增加版本機制改成樂觀鎖。
insert into lock set lock_name='TaskA' , resource='鎖住的業(yè)務',version=1,gmt_create=now() success: 獲取到鎖 failed:放棄操作 釋放鎖
樂觀鎖實現(xiàn)
針對樂觀鎖場景,舉個具體業(yè)務,在后臺系統(tǒng)中經(jīng)常使用大json擴展字段存儲業(yè)務屬性,在涉及部分更新時,需要先查詢出來,合并數(shù)據(jù),寫入到DB,這個過程中如果存在并發(fā),則很容易造成數(shù)據(jù)丟失,因此需要使用鎖來保證數(shù)據(jù)一致性,相應操作如下所示,針對樂觀鎖,不存在死鎖,因此這里直接存放業(yè)務id字段,保證每一個業(yè)務id有一條對應的記錄,并且不需要對應的定時器清除。
select * from lock where lock_name='業(yè)務名稱', resource='業(yè)務id'; 不存在: insert into lock set lock_name='業(yè)務名稱', resource='業(yè)務id' , version=1; 獲取版本: version 業(yè)務操作: 取數(shù)據(jù),合并數(shù)據(jù),寫回數(shù)據(jù) 寫回到DB: update lock set version=version+1 where lock_name='業(yè)務名稱' and resource='業(yè)務id' and version= #{version}; 寫回成功: 操作成功 寫回失敗: 回滾事務,從頭操作
樂觀鎖寫入失敗會回滾整個事務,因此如果寫入沖突很頻繁的場景不適合使用樂觀鎖,大量的事務回滾會給DB巨大壓力,最終影響到具體業(yè)務系統(tǒng)。
總結(jié)
分布式鎖的原理實際上很容易理解,難的是如何在具體業(yè)務場景上選擇最合適的方案。無論是哪一種鎖方案都是與業(yè)務密切關(guān)聯(lián),總之沒有完美的分布式鎖方案,只有最適合當前業(yè)務的鎖方案。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對腳本之家的支持。
相關(guān)文章
mysql優(yōu)化的重要參數(shù) key_buffer_size table_cache
MySQL服務器端的參數(shù)有很多,但是對于大多數(shù)初學者來說,眾多的參數(shù)往往使得我們不知所措,但是哪些參數(shù)是需要我們調(diào)整的,哪些對服務器的性能影響最大呢2016-05-05MySQL中的CONCAT()函數(shù):輕松拼接字符串的利器
這篇文章主要介紹了MySQL中的CONCAT()函數(shù):輕松拼接字符串的利器,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-04-04mysql升級到5.7時,wordpress導數(shù)據(jù)報錯1067的問題
小編最近把mysql升級到5.7了,wordpress導數(shù)據(jù)報錯,導入數(shù)據(jù)庫時報1067 – Invalid default value for ‘字段名’的問題,怎么解決這個問題,下面小編把我的解決方案分享到腳本之家平臺供大家參考,希望對大家有所幫助2021-05-055招帶你輕松優(yōu)化MySQL count(*)查詢性能
最近在公司優(yōu)化了幾個慢查詢接口的性能,總結(jié)了一些心得體會拿出來跟大家一起分享一下,文中的示例代碼講解詳細,希望對大家會有所幫助2022-11-11Mysql技術(shù)內(nèi)幕之InnoDB鎖的深入講解
這篇文章主要給大家介紹了關(guān)于Mysql技術(shù)內(nèi)幕之InnoDB鎖的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12