基于Redis實現分布式鎖實戰

背景
在很多互聯網產品應用中,有些場景需要加鎖處理,比如:秒殺,全局遞增ID,樓層生成等等。大部分的解決方案是基于DB實現的,Redis為單進程單線程模式,采用隊列模式將并發訪問變成串行訪問,且多客戶端對Redis的連接并不存在競爭關系。其次Redis提供一些命令SETNX,GETSET,可以方便實現分布式鎖機制。

Redis命令介紹
使用Redis實現分布式鎖,有兩個重要函數需要介紹

SETNX命令(SET if Not eXists)
語法:
SETNX key value
功能:
當且僅當 key 不存在,將 key 的值設為 value ,并返回1;若給定的 key 已經存在,則 SETNX 不做任何動作,并返回0。

GETSET命令
語法:
GETSET key value
功能:
將給定 key 的值設為 value ,并返回 key 的舊值 (old value),當 key 存在但不是字符串類型時,返回一個錯誤,當key不存在時,返回nil。

GET命令
語法:
GET key
功能:
返回 key 所關聯的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令
語法:
DEL key [KEY …]
功能:
刪除給定的一個或多個 key ,不存在的 key 會被忽略。

兵貴精,不在多。分布式鎖,我們就依靠這四個命令。但在具體實現,還有很多細節,需要仔細斟酌,因為在分布式并發多進程中,任何一點出現差錯,都會導致死鎖,hold住所有進程。

加鎖實現

SETNX 可以直接加鎖操作,比如說對某個關鍵詞foo加鎖,客戶端可以嘗試
SETNX foo.lock <current unix time>

如果返回1,表示客戶端已經獲取鎖,可以往下操作,操作完成后,通過
DEL foo.lock

命令來釋放鎖。
如果返回0,說明foo已經被其他客戶端上鎖,如果鎖是非堵塞的,可以選擇返回調用。如果是堵塞調用調用,就需要進入以下個重試循環,直至成功獲得鎖或者重試超時。理想是美好的,現實是殘酷的。僅僅使用SETNX加鎖帶有競爭條件的,在某些特定的情況會造成死鎖錯誤。

處理死鎖

在上面的處理方式中,如果獲取鎖的客戶端端執行時間過長,進程被kill掉,或者因為其他異常崩潰,導致無法釋放鎖,就會造成死鎖。所以,需要對加鎖要做時效性檢測。因此,我們在加鎖時,把當前時間戳作為value存入此鎖中,通過當前時間戳和Redis中的時間戳進行對比,如果超過一定差值,認為鎖已經時效,防止鎖無限期的鎖下去,但是,在大并發情況,如果同時檢測鎖失效,并簡單粗暴的刪除死鎖,再通過SETNX上鎖,可能會導致競爭條件的產生,即多個客戶端同時獲取鎖。

C1獲取鎖,并崩潰。C2和C3調用SETNX上鎖返回0后,獲得foo.lock的時間戳,通過比對時間戳,發現鎖超時。
C2 向foo.lock發送DEL命令。
C2 向foo.lock發送SETNX獲取鎖。
C3 向foo.lock發送DEL命令,此時C3發送DEL時,其實DEL掉的是C2的鎖。
C3 向foo.lock發送SETNX獲取鎖。

此時C2和C3都獲取了鎖,產生競爭條件,如果在更高并發的情況,可能會有更多客戶端獲取鎖。所以,DEL鎖的操作,不能直接使用在鎖超時的情況下,幸好我們有GETSET方法,假設我們現在有另外一個客戶端C4,看看如何使用GETSET方式,避免這種情況產生。

C1獲取鎖,并崩潰。C2和C3調用SETNX上鎖返回0后,調用GET命令獲得foo.lock的時間戳T1,通過比對時間戳,發現鎖超時。
C4 向foo.lock發送GESET命令,
GETSET foo.lock <current unix time>
并得到foo.lock中老的時間戳T2

如果T1=T2,說明C4獲得時間戳。
如果T1!=T2,說明C4之前有另外一個客戶端C5通過調用GETSET方式獲取了時間戳,C4未獲得鎖。只能sleep下,進入下次循環中。

現在唯一的問題是,C4設置foo.lock的新時間戳,是否會對鎖產生影響。其實我們可以看到C4和C5執行的時間差值極小,并且寫入foo.lock中的都是有效時間錯,所以對鎖并沒有影響。
為了讓這個鎖更加強壯,獲取鎖的客戶端,應該在調用關鍵業務時,再次調用GET方法獲取T1,和寫入的T0時間戳進行對比,以免鎖因其他情況被執行DEL意外解開而不知。以上步驟和情況,很容易從其他參考資料中看到。客戶端處理和失敗的情況非常復雜,不僅僅是崩潰這么簡單,還可能是客戶端因為某些操作被阻塞了相當長時間,緊接著 DEL 命令被嘗試執行(但這時鎖卻在另外的客戶端手上)。也可能因為處理不當,導致死鎖。還有可能因為sleep設置不合理,導致Redis在大并發下被壓垮。最為常見的問題還有

GET返回nil時應該走那種邏輯?

第一種走超時邏輯
C1客戶端獲取鎖,并且處理完后,DEL掉鎖,在DEL鎖之前。C2通過SETNX向foo.lock設置時間戳T0 發現有客戶端獲取鎖,進入GET操作。
C2 向foo.lock發送GET命令,獲取返回值T1(nil)。
C2 通過T0>T1+expire對比,進入GETSET流程。
C2 調用GETSET向foo.lock發送T0時間戳,返回foo.lock的原值T2
C2 如果T2=T1相等,獲得鎖,如果T2!=T1,未獲得鎖。

第二種情況走循環走setnx邏輯
C1客戶端獲取鎖,并且處理完后,DEL掉鎖,在DEL鎖之前。C2通過SETNX向foo.lock設置時間戳T0 發現有客戶端獲取鎖,進入GET操作。
C2 向foo.lock發送GET命令,獲取返回值T1(nil)。
C2 循環,進入下一次SETNX邏輯

兩種邏輯貌似都是OK,但是從邏輯處理上來說,第一種情況存在問題。當GET返回nil表示,鎖是被刪除的,而不是超時,應該走SETNX邏輯加鎖。走第一種情況的問題是,正常的加鎖邏輯應該走SETNX,而現在當鎖被解除后,走的是GETST,如果判斷條件不當,就會引起死鎖,很悲催,我在做的時候就碰到了,具體怎么碰到的看下面的問題

GETSET返回nil時應該怎么處理?

C1和C2客戶端調用GET接口,C1返回T1,此時C3網絡情況更好,快速進入獲取鎖,并執行DEL刪除鎖,C2返回T2(nil),C1和C2都進入超時處理邏輯。
C1 向foo.lock發送GETSET命令,獲取返回值T11(nil)。
C1 比對C1和C11發現兩者不同,處理邏輯認為未獲取鎖。
C2 向foo.lock發送GETSET命令,獲取返回值T22(C1寫入的時間戳)。
C2 比對C2和C22發現兩者不同,處理邏輯認為未獲取鎖。

此時C1和C2都認為未獲取鎖,其實C1是已經獲取鎖了,但是他的處理邏輯沒有考慮GETSET返回nil的情況,只是單純的用GET和GETSET值就行對比,至于為什么會出現這種情況?一種是多客戶端時,每個客戶端連接Redis的后,發出的命令并不是連續的,導致從單客戶端看到的好像連續的命令,到Redis server后,這兩條命令之間可能已經插入大量的其他客戶端發出的命令,比如DEL,SETNX等。第二種情況,多客戶端之間時間不同步,或者不是嚴格意義的同步。

時間戳的問題

我們看到foo.lock的value值為時間戳,所以要在多客戶端情況下,保證鎖有效,一定要同步各服務器的時間,如果各服務器間,時間有差異。時間不一致的客戶端,在判斷鎖超時,就會出現偏差,從而產生競爭條件。
鎖的超時與否,嚴格依賴時間戳,時間戳本身也是有精度限制,假如我們的時間精度為秒,從加鎖到執行操作再到解鎖,一般操作肯定都能在一秒內完成。這樣的話,我們上面的CASE,就很容易出現。所以,最好把時間精度提升到毫秒級。這樣的話,可以保證毫秒級別的鎖是安全的。

分布式鎖的問題

1:必要的超時機制:獲取鎖的客戶端一旦崩潰,一定要有過期機制,否則其他客戶端都降無法獲取鎖,造成死鎖問題。
2:分布式鎖,多客戶端的時間戳不能保證嚴格意義的一致性,所以在某些特定因素下,有可能存在鎖串的情況。要適度的機制,可以承受小概率的事件產生。
3:只對關鍵處理節點加鎖,良好的習慣是,把相關的資源準備好,比如連接數據庫后,調用加鎖機制獲取鎖,直接進行操作,然后釋放,盡量減少持有鎖的時間。
4:在持有鎖期間要不要CHECK鎖,如果需要嚴格依賴鎖的狀態,最好在關鍵步驟中做鎖的CHECK檢查機制,但是根據我們的測試發現,在大并發時,每一次CHECK鎖操作,都要消耗掉幾個毫秒,而我們的整個持鎖處理邏輯才不到10毫秒,玩客沒有選擇做鎖的檢查。
5:sleep學問,為了減少對Redis的壓力,獲取鎖嘗試時,循環之間一定要做sleep操作。但是sleep時間是多少是門學問。需要根據自己的Redis的QPS,加上持鎖處理時間等進行合理計算。
6:至于為什么不使用Redis的muti,expire,watch等機制,可以查一參考資料,找下原因。

7:想要深入系統了解分布式技術的話,我在這里給大家推薦一個架構方面的交流學習群:650385180,里面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發、高性能、分布式、微服務架構的原理,JVM性能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,相信對于已經工作和遇到技術瓶頸的碼友,在這個群里會有你需要的內容。

鎖測試數據

未使用sleep
第一種,鎖重試時未做sleep。單次請求,加鎖,執行,解鎖時間?


可以看到加鎖和解鎖時間都很快,當我們使用

ab -n1000 -c100 'http://sandbox6.wanke.etao.com/test/test_sequence.php?tbpm=t'
AB 并發100累計1000次請求,對這個方法進行壓測時。?


我們會發現,獲取鎖的時間變成,同時持有鎖后,執行時間也變成,而delete鎖的時間,將近10ms時間,為什么會這樣?
1:持有鎖后,我們的執行邏輯中包含了再次調用Redis操作,在大并發情況下,Redis執行明顯變慢。
2:鎖的刪除時間變長,從之前的0.2ms,變成9.8ms,性能下降近50倍。
在這種情況下,我們壓測的QPS為49,最終發現QPS和壓測總量有關,當我們并發100總共100次請求時,QPS得到110多。當我們使用sleep時

使用Sleep時

單次執行請求時

我們看到,和不使用sleep機制時,性能相當。當時用相同的壓測條件進行壓縮時?

獲取鎖的時間明顯變長,而鎖的釋放時間明顯變短,僅是不采用sleep機制的一半。當然執行時間變成就是因為,我們在執行過程中,重新創建數據庫連接,導致時間變長的。同時我們可以對比下Redis的命令執行壓力情況?

上圖中細高部分是為未采用sleep機制的時的壓測圖,矮胖部分為采用sleep機制的壓測圖,通上圖看到壓力減少50%左右,當然,sleep這種方式還有個缺點QPS下降明顯,在我們的壓測條件下,僅為35,并且有部分請求出現超時情況。不過綜合各種情況后,我們還是決定采用sleep機制,主要是為了防止在大并發情況下把Redis壓垮,很不行,我們之前碰到過,所以肯定會采用sleep機制。

文章轉載自CSDN:https://blog.csdn.net/ugg/article/details/41894947

參考資料

http://www.worlduc.com/FileSystem/18/2518/590664/9f63555e6079482f831c8ab1dcb8c19c.pdf
http://redis.io/commands/setnx
http://www.blogjava.net/caojianhua/archive/2013/01/28/394847.html

?

轉載于:https://www.cnblogs.com/lfs2640666960/p/9307827.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/389151.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/389151.shtml
英文地址,請注明出處:http://en.pswp.cn/news/389151.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據分析 績效_如何在績效改善中使用數據分析

數據分析 績效Imagine you need to do a bank transaction, but the website is so slow. The page takes so much time to load, all you can see is a blue circle.想象您需要進行銀行交易&#xff0c;但是網站是如此緩慢。 該頁面需要花費很多時間來加載&#xff0c;您只能看…

隱私策略_隱私圖標

隱私策略During its 2020 Worldwide Developers Conference, Apple spent time on one of today’s hottest topics — privacy. During the past couple of years, Apple has been rolling out various public campaigns aiming to position itself as a company that respect…

Java中的數組

一、數組的定義type[] arrayName;type arrayName[]; 推薦第一種 二、數組的初始化 含義&#xff1a;所謂的初始化&#xff0c;就是為數組的數組元素分配內存空間&#xff0c;并為每個數組元素賦初始值 &#xff08;1&#xff09;靜態初始化&#xff1a;arrayName new type[…

您一直在尋找5+個簡單的一線工具來提升Python可視化效果

Insightful and aesthetic visualizations don’t have to be a pain to create. This article will prevent 5 simple one-liners you can add to your code to increase its style and informational value.富有洞察力和美學的可視化不必費心創建。 本文將防止您添加到代碼中…

用C#編寫的代碼經C#編譯器后,并非生成本地代碼而是生成托管代碼

用C#編寫的代碼經C#編譯器后&#xff0c;并非生成本地代碼而是生成托管代碼。也就是說&#xff0c;程序集在打包時是連同CLR一起打包的。在客戶端的機器上&#xff0c;CLR一行行的讀取IL&#xff0c;在讀取每行IL時&#xff0c;CLR利用JIT編譯器將IL編譯成本地的CPU指令。若要節…

figma 安裝插件_彩色濾光片Figma插件,用于色盲

figma 安裝插件So as a UX Designer, it is important to design with disabilities in mind. One of these is color blindness. It is important to make sure important information on your product is legible to everyone. This is why I like using this tool:因此&…

服務器運維

1.服務器和網站漏洞檢測&#xff0c;對Web漏洞、弱口令、潛在的惡意行為、違法信息等進行定期掃描&#xff1b;代碼的定期檢查&#xff0c;漏洞檢查及服務器安全加固 2.服務器數據備份&#xff0c;包括網站程序文件備份&#xff0c;數據庫文件備份、配置文件備份&#xff0c;如…

產品觀念:更好的捕鼠器_故事很重要:為什么您需要成為更好的講故事的人

產品觀念&#xff1a;更好的捕鼠器重點 (Top highlight)Telling a compelling story helps you get your point across effectively else you get lost in translation.講一個引人入勝的故事可以幫助您有效地傳達觀點&#xff0c;否則您會迷失在翻譯中。 Great stories happen…

7月15號day7總結

今天復習了springMVC的框架搭建。 思維導圖&#xff1a; 轉載于:https://www.cnblogs.com/kangy123/p/9315919.html

關于注意力的問題

問題&#xff1a;一旦持續的注意力分散和精力無法集中成為習慣性動作&#xff0c;這將成為一個嚴重的問題。 實質&#xff1a;加強有意識的集中程度和持續時間&#xff0c;盡量避免無意識注意對大腦的干擾。 不要浪費注意力。大腦以天為周期&#xff0c;每天注意力是有限的。T…

設計師的10種范式轉變

For $250, a business can pay a graphic designer to create a logo for their business. Or, for $10,000 a business can hire a graphic designer to form a design strategy that contextually places the business’s branding in a stronghold against the market it’s…

面向Tableau開發人員的Python簡要介紹(第2部分)

用PYTHON探索數據 (EXPLORING DATA WITH PYTHON) And we’re back! Let’s pick up where we left off in the first article of this series and use the visual we built there as a starting point.我們回來了&#xff01; 讓我們從在本系列的第一篇文章中停下來的地方開始&…

GAC中的所有的Assembly都會存放在系統目錄%winroot%/assembly下面

是的&#xff0c;GAC中的所有的Assembly都會存放在系統目錄"%winroot%/assembly下面。放在系統目錄下的好處之一是可以讓系統管理員通過用戶權限來控制Assembly的訪問。 關于GAC本身&#xff0c;上面redcaff_l所引述的一段話正是MSDN中對GAC的定義。GAC全稱是Global A…

Mysql(三) Mysq慢查詢日志

Mysql Slow Query Log MYSQL慢查詢日志是用來記錄執行時間超過指定時間的查詢語句。通過慢查詢日志&#xff0c;可以查找出哪些查詢語句的執行效率很低&#xff0c;以便進行優化。一般建議開啟&#xff0c;它對服務器性能的影響微乎其微&#xff0c;但是可以記錄mysql服務器上執…

繪制基礎知識-canvas paint

先來看一下Canvas Canvas 用來提供draw方法的調用。繪制東西需要4個基本的組建&#xff1a;一個bitmap用來存放像素&#xff0c;一個canvas用來提供draw方法的調用(往bitmap里寫入)&#xff0c;原始繪制元素&#xff08;e.g.Rect, Path, text,Bitmap&#xff09;, 一個paint。 …

Python - 調試Python代碼的方法

調試(debug) 將可疑環節的變量逐步打印出來&#xff0c;從而檢查哪里是否有錯。讓程序一部分一部分地運行起來。從核心功能開始&#xff0c;寫一點&#xff0c;運行一點&#xff0c;再修改一點。利用工具&#xff0c;例如一些IDE中的調試功能&#xff0c;提高調試效率。Python …

設計組合中的10個嚴重錯誤可能會導致您喪命

As an agency co-founder and design lead, I’ve been participating in many recruitment processes. I’ve seen hundreds of portfolios and CVs of aspiring designers. If you’re applying for a UI designer position, it is good to have some things in mind and to …

netflix_Netflix的計算因果推論

netflixJeffrey Wong, Colin McFarland杰弗里黃 &#xff0c; 科林麥克法蘭 Every Netflix data scientist, whether their background is from biology, psychology, physics, economics, math, statistics, or biostatistics, has made meaningful contributions to the way…

算法題庫網站

Google Code Jam&#xff08;GCJ&#xff09;Peking University Online Judge&#xff08;POJ&#xff09;CodeForces&#xff08;CF&#xff09;LeetCode&#xff08;LC&#xff09;Aizu Online Judge&#xff08;AOJ&#xff09;

org.dom4j.DocumentException: null Nested exception: null解決方法

由于最近在學習使用Spring架構&#xff0c;經常會遇到與xml文檔打交道&#xff0c;今天遇到了此問題&#xff0c;特來分享一下解決方案。 出錯原因&#xff1a; 很明顯是因為找不到文件路徑。這個原因是因為我使用了*.clas.getResourceAsStream&#xff08;xmlFilePath&#xf…