本文首發於: 基於Redis的分佈式鎖和Redlock算法
微信公衆號:後端技術指南針
1 前言html
前面寫了4篇Redis底層實現和工程架構相關文章,感興趣的讀者能夠回顧一下:前端
今天開始來和你們一塊兒學習一下Redis實際應用篇,會寫幾個Redis的常見應用。面試
在我看來Redis最爲典型的應用就是做爲分佈式緩存系統,其餘的一些應用本質上並非殺手鐗功能,是基於Redis支持的數據類型和分佈式架構來實現的,屬於小而美的應用。redis
結合筆者的平常工做,今天和你們一塊兒研究下基於Redis的分佈式鎖和Redlock算法的一些事情。算法
1. 鎖的雙面性數據庫
如今咱們寫的程序基本上都有必定的併發性,要麼單臺多進線程、要麼多臺機器集羣化,在僅讀的場景下是不須要加鎖的,由於數據是一致的,在讀寫混合或者寫場景下若是不加以限制和約束就會形成寫混亂數據不一致的狀況。編程
若是業務安全和正確性沒法保證,再多的併發也是無心義的。後端
這個不禁得讓我想起一個趣圖:緩存
高併發多半是考驗大家公司的基礎架構是否強悍,合理正確地使用鎖纔是我的能力的體現。安全
凡事基本上都是雙面的,鎖能夠在必定程度上保證數據的一致性,可是鎖也意味着維護和使用的複雜性,固然也伴隨着性能的損耗,我見過的最大的鎖可能就是CPython解釋器的全局解釋器鎖GIL了。
沒辦法 好可怕 那個鎖 不像話--《說鎖就鎖》
鎖使用不當不但解決不了數據混亂問題,甚至會帶來諸如死鎖等更多問題,通俗地說死鎖現象:
幾年前會出現這樣的場景:在異地須要買火車票回老家,可是身份證丟了沒法購票,補辦身份證又須要本人坐火車回老家戶籍管理處,就這樣生活太難。
2. 無鎖化編程
既然鎖這麼難以把控,那不得不思考有沒有無鎖的高併發。
無鎖編程也是一個很是有意思的話題,後續能夠寫一篇聊聊這個話題,本次就只提一下,要打開思路,不要被困在凡是併發必須加鎖的思惟定勢。
在某些特定場景下會選擇一種並行轉串行的思路,從而儘可能避免鎖的使用,舉個栗子:
Post請求: abc.def/setdata?req…
假若有一個上述的post請求的URI部分是個覆蓋寫操做,reqid=abc123789def,服務部署在多臺機器,在大前端將流量轉發到Nginx以後根據reqid進行哈希,Nginx的配置大概是這樣的:
upstream myservice { #根據參數進行Hash分配 hash $urlkey; server localhost:5000; server localhost:5001; server localhost:5002; } 複製代碼
通過Nginx負載均衡相同reqid的請求將被轉發到一臺機器上,固然你可能會說若是集羣的機器動態調整呢?我只能說不要考慮那麼多那麼充分,工程化去設計便可。
然而轉發到一臺機器仍然沒法保證串行處理,由於單機仍然是多線程的,咱們仍然須要將全部的reqid數據放到同一個線程處理,最終保證線程內串行,這個就須要藉助於線程池的管理者Disper按照reqid哈希取模來進行多線程的負載均衡。
通過Nginx和線程內負載均衡,最終相同的reqid都將在線程內串行處理,有效避免了鎖的使用,固然這種設計可能在reqid不均衡時形成線程飢餓,不太高併發大量請求的狀況下仍是能夠的。
只描述不畫圖 就等於沒說:
3. 單機鎖和分佈式鎖
鎖依據使用範圍可簡單分爲:單機鎖和分佈式鎖。
Linux提供系統級單機鎖,這類鎖能夠實現線程同步和互斥資源的共享,單機鎖實現了機器內部線程之間對共享資源的併發控制。
在分佈式部署高併發場景下,常常會遇到資源的互斥訪問的問題,最有效最廣泛的方法是給共享資源或者對共享資源的操做加一把鎖。
分佈式鎖是控制分佈式系統之間同步訪問共享資源的一種方式,用於在分佈式系統中協調他們之間的動做。
1. 分佈式鎖的實現簡介
分佈式CAP理論告訴咱們須要作取捨:
任何一個分佈式系統有三大特性:一致性Consistency、可用性Availability和分區容錯性Partition Tolerance,可是因爲網絡分區不受人爲控制,在網絡發生分區時,咱們必須在可用性和一致性兩者中選擇之一。
在互聯網領域的絕大多數的場景中,都須要犧牲強一致性來換取系統的高可用性,系統每每只保證最終一致性。在不少場景中爲了保證數據的最終一致性,須要不少的技術方案來支持,好比分佈式事務、分佈式鎖等。
分佈式鎖通常有三種實現方式:
2. 分佈式鎖須要具有的條件
分佈式鎖在應用於分佈式系統環境相比單機鎖更爲複雜,本文講述基於Redis的分佈式鎖實現,該鎖須要具有一些特性:
3. 基於單Redis節點的分佈式鎖
本文的重點是基於多Redis節點的Redlock算法,不過在展開這個算法以前,有必要提一下單Redis節點分佈式鎖原理以及演進,由於Redlock算法是基於此改進的。
最初分佈式鎖藉助於setnx和expire命令,可是這兩個命令不是原子操做,若是執行setnx以後獲取鎖可是此時客戶端掛掉,這樣沒法執行expire設置過時時間就致使鎖一直沒法被釋放,所以在2.8版本中Antirez爲setnx增長了參數擴展,使得setnx和expire具有原子操做性。
在單Matster-Slave的Redis系統中,正常狀況下Client向Master獲取鎖以後同步給Slave,若是Client獲取鎖成功以後Master節點掛掉,而且未將該鎖同步到Slave,以後在Sentinel的幫助下Slave升級爲Master可是並無以前未同步的鎖的信息,此時若是有新的Client要在新Master獲取鎖,那麼將可能出現兩個Client持有同一把鎖的問題,來看個圖來想下這個過程:
爲了保證本身的鎖只能本身釋放須要增長惟一性的校驗,綜上基於單Redis節點的獲取鎖和釋放鎖的簡單過程,使用lua腳本實現以下:
// 獲取鎖 unique_value做爲惟一性的校驗 SET resource_name unique_value NX PX 30000 // 釋放鎖 比較unique_value是否相等 避免誤釋放 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end複製代碼
這就是基於單Redis的分佈式鎖的幾個要點。
Redlock算法是Antirez在單Redis節點基礎上引入的高可用模式。
在Redis的分佈式環境中,咱們假設有N個徹底互相獨立的Redis節點,在N個Redis實例上使用與在Redis單實例下相同方法獲取鎖和釋放鎖。
如今假設有5個Redis主節點(大於3的奇數個),這樣基本保證他們不會同時都宕掉,獲取鎖和釋放鎖的過程當中,客戶端會執行如下操做:
上述的5個步驟是Redlock算法的重要過程,也是面試的熱點,有心的讀者仍是記錄一下吧!
1.關於馬丁·克萊普曼博士
2016年2月8號分佈式系統的專家馬丁·克萊普曼博士(Martin Kleppmann)在一篇文章How to do distributed locking 指出分佈式鎖設計的一些原則而且對Antirez的Redlock算法提出了一些質疑。
筆者找到了馬丁·克萊普曼博士的我的網站以及一些簡介,一塊兒看下:
用搜狗翻譯看一下:
1.我是劍橋大學計算機科學與技術系的高級研究助理和附屬講師,由勒弗烏爾姆信託早期職業獎學金和艾薩克牛頓信託基金資助。我致力於本地優先的協做軟件和分佈式系統安全。
2.我也是劍橋科珀斯克里斯蒂學院計算機科學研究的研究員和主任,我在那裏從事本科教學。
3.2017年,我爲奧雷利出版了一本名爲《設計數據密集型應用》的書。它涵蓋了普遍的數據庫和分佈式數據處理系統的體系結構,是該出版社最暢銷書之一。
4.我常常在會議上發言,個人演講錄音已經被觀看了超過15萬次。
5.我參與過各類開源項目,包括自動合併、Apache Avro和Apache Samza。
6.2007年至2014年間,我是一名工業軟件工程師和企業家。我共同創立了Rapportive(2012年被領英收購)和Go Test(2009年被紅門軟件收購)。
7.我創做了幾部音樂做品,包括《二月之死》(德語),這是唐克·德拉克特對該書的音樂戲劇改編,於2007年首映,共有150人蔘與。
大牛就是大牛,能教書、能出書、能寫開源軟件、能創業、能寫音樂劇,優秀的人哪方面也優秀,服氣了。
2.馬丁博士文章的主要觀點
馬丁·克萊普曼在文章中談及了分佈式系統的不少基礎問題,特別是分佈式計算的異步模型,文章分爲兩大部分前半部分講述分佈式鎖的一些原則,後半部分針對Redlock提出一些見解:
針對fencing機制馬丁給出了一個時序圖:
獲取鎖的客戶端在持有鎖時可能會暫停一段較長的時間,儘管鎖有一個超時時間,避免了崩潰的客戶端可能永遠持有鎖而且永遠不會釋放它,可是若是客戶端的暫停持續的時間長於鎖的到期時間,而且客戶沒有意識到它已經到期,那麼它可能會繼續進行一些不安全的更改,換言之因爲客戶端阻塞致使的持有的鎖到期而不自知。
針對這種狀況馬丁指出要增長fencing機制,具體來講是fencing token隔離令牌機制,一樣給出了一張時序圖:
客戶端1得到鎖而且得到序號爲33的令牌,但隨後它進入長時間暫停,直至鎖超時過時,客戶端2獲取鎖而且得到序號爲34的令牌,而後將其寫入發送到存儲服務。隨後,客戶端1復活並將其寫入發送到存儲服務,然而存儲服務器記得它已經處理了具備較高令牌號的寫入34,所以它拒絕令牌33的請求。
Redlock算法並無這種惟一且遞增的fencing token生成機制,這也意味着Redlock算法不能避免因爲客戶端阻塞帶來的鎖過時後的操做問題,所以是不安全的。
這個觀點筆者以爲並無完全解決問題,由於若是客戶端1的寫入操做是必需要執行成功的,可是因爲阻塞超時沒法再寫入一樣就產生了一個錯誤的結果,客戶端2將可能在這個錯誤的結果上進行操做,那麼任何操做都註定是錯誤的。
3.馬丁博士對Redlock的質疑
馬丁·克萊普曼指出Redlock是個強依賴系統時間的算法,這樣就可能帶來不少不一致問題,他給出了個例子一塊兒看下:
假設多節點Redis系統有五個節點A/B/C/D/E和兩個客戶端C1和C2,若是其中一個Redis節點上的時鐘向前跳躍會發生什麼?
分佈式異步模型:
上面這種狀況之因此有可能發生,本質上是由於Redlock的安全性對Redis節點系統時鐘有強依賴,一旦系統時鐘變得不許確,算法的安全性也就沒法保證。
馬丁實際上是要指出分佈式算法研究中的一些基礎性問題,好的分佈式算法應該基於異步模型,算法的安全性不該該依賴於任何記時假設。
分佈式異步模型中進程和消息可能會延遲任意長的時間,系統時鐘也可能以任意方式出錯。這些因素不該該影響它的安全性,只可能影響到它的活性,即便在很是極端的狀況下,算法最可能是不能在有限的時間內給出結果,而不該該給出錯誤的結果,這樣的算法在現實中是存在的好比Paxos/Raft,按這個標準衡量Redlock的安全級別是達不到的。
4.馬丁博士文章結論和基本觀點
馬丁表達了本身的觀點,把鎖的用途分爲兩種:
最後馬丁出了以下的結論:
馬丁認爲Redlock算法是個糟糕的選擇,由於它不三不四:出於效率選擇來講,它過於重量級和昂貴,出於正確性選擇它又不夠安全。
馬丁的那篇文章是在2016.2.8發表以後Antirez反應很快,他發表了"Is Redlock safe?"進行逐一反駁,文章地址以下:antirez.com/news/101
Antirez認爲馬丁的文章對於Redlock的批評能夠歸納爲兩個方面:
Antirez對這兩方面分別進行了細緻地反駁。
關於fencing機制
Antirez提出了質疑:既然在鎖失效的狀況下已經存在一種fencing機制能繼續保持資源的互斥訪問了,那爲何還要使用一個分佈式鎖而且還要求它提供那麼強的安全性保證呢?
退一步講Redlock雖然提供不了遞增的fencing token隔離令牌,但利用Redlock產生的隨機字符串能夠達到一樣的效果,這個隨機字符串雖然不是遞增的,但倒是惟一的。
關於記時假設
Antirez針對算法在記時模型假設集中反駁,馬丁認爲Redlock失效狀況主要有三種:
後兩種狀況來講,Redlock在當初之處進行了相關設計和考量,對這兩種問題引發的後果有必定的抵抗力。
時鐘跳躍對於Redlock影響較大,這種狀況一旦發生Redlock是無法正常工做的。
Antirez指出Redlock對系統時鐘的要求並不須要徹底精確,只要偏差不超過必定範圍不會產生影響,在實際環境中是徹底合理的,經過恰當的運維徹底能夠避免時鐘發生大的跳動。
分佈式系統自己就很複雜,機制和理論的效果須要必定的數學推導做爲依據,馬丁和Antirez都是這個領域的專家,對於一些問題都會有本身的見解和思考,更重要的是不少時候問題自己並無完美的解決方案。
此次爭論是分佈式系統領域很是好的一次思想的碰撞,不少網友都發表了本身的見解和認識,馬丁博士也在Antirez作出反應一段時間以後再次發表了本身的一些觀點:
For me, this is the most important point: I don’t care who is right or wrong in this debate — I care about learning from others’ work, so that we can avoid repeating old mistakes, and make things better in future. So much great work has already been done for us: by standing on the shoulders of giants, we can build better software.
By all means, test ideas by arguing them and checking whether they stand up to scrutiny by others. That’s part of the learning process. But the goal should be to learn, not to convince others that you are right. Sometimes that just means to stop and think for a while.
簡單翻譯下就是:
對馬丁而言並不在意誰對誰錯,他更關心於從他人的工做中汲取經驗來避免本身的錯誤重複工做,正如咱們是站在巨人的肩膀上才能作出更好的成績。
另外經過別人的爭論和檢驗才更能讓本身的想法經得起考驗,咱們的目標是相互學習而不是說服別人相信你是對的,所謂一人計短,思考辯駁才能更加接近真理。
在Antirez發表文章以後世界各地的分佈式系統專家和愛好者都積極發表本身的見解,筆者在評論中發現了一個熟悉的名字:
鐵蕾大神的兩篇文章寫的很是好,本文從中作了不少參考,也是鐵蕾大神的文章讓筆者瞭解到這場精彩的華山論劍,感興趣的能夠直接搜索閱讀參考3和4。