1. 程式人生 > 其它 >為什麼說spring讓Java變得更好?年薪60W必備

為什麼說spring讓Java變得更好?年薪60W必備

為什麼說spring讓Java變得更好?年薪60W必備

分散式鎖的坑

高併發場景下的問題

以下問題不是說在併發不高的場景下不容易出現,只是在高併發場景下出現的概率更高些而已。

效能問題來自於以下兩方面:

①獲取鎖的時間上。如果 Redlock 運用在高併發的場景下,存在 N 個 Master 節點,一個一個去請求,耗時會比較長,從而影響效能。

這個好解決,通過上面描述不難發現,從多個節點獲取鎖的操作並不是一個同步操作,可以是非同步操作,這樣可以多個節點同時獲取。

即使是並行處理的,還是得預估好獲取鎖的時間,保證鎖的 TTL>獲取鎖的時間+任務處理時間。

②被加鎖的資源太大。加鎖的方案本身就是會為了正確性而犧牲併發的,犧牲和資源大小成正比,這個時候可以考慮對資源做拆分。

拆分的方式有如下兩種:

①從業務上將鎖住的資源拆分成多段,每段分開加鎖。比如,我要對一個商戶做若干個操作,操作前要鎖住這個商戶,這時我可以將若干個操作拆成多個獨立的步驟分開加鎖,提高併發。

②用分桶的思想,將一個資源拆分成多個桶,一個加鎖失敗立即嘗試下一個。比如批量任務處理的場景,要處理 200w 個商戶的任務,為了提高處理速度,用多個執行緒,每個執行緒取 100 個商戶處理,就得給這 100 個商戶加鎖。

如果不加處理,很難保證同一時刻兩個執行緒加鎖的商戶沒有重疊,這時可以按一個維度。

比如某個標籤,對商戶進行分桶,然後一個任務處理一個分桶,處理完這個分桶再處理下一個分桶,減少競爭。

重試的問題:無論是簡單實現還是 Redlock 實現,都會有重試的邏輯。

如果直接按上面的演算法實現,是會存在多個 Client 幾乎在同一時刻獲取同一個鎖,然後每個 Client 都鎖住了部分節點,但是沒有一個 Client 獲取大多數節點的情況。

解決的方案也很常見,在重試的時候讓多個節點錯開,錯開的方式就是在重試時間中加一個隨機時間。這樣並不能根治這個問題,但是可以有效緩解問題,親試有效。

節點宕機

對於單 Master 節點且沒有做持久化的場景,宕機就掛了,這個就必須在實現上支援重複操作,自己做好冪等。對於多 Master 的場景,比如 Redlock,我們來看這樣一個場景:

  • 假設有 5 個 Redis 的節點:A、B、C、D、E,沒有做持久化。

  • Client1 從 A、B、C 這3 個節點獲取鎖成功,那麼 client1 獲取鎖成功。

  • 節點 C 掛了。

  • Client2 從 C、D、E 獲取鎖成功,client2 也獲取鎖成功,那麼在同一時刻 Client1 和 Client2 同時獲取鎖,Redlock 被玩壞了。

怎麼解決呢?最容易想到的方案是開啟持久化。持久化可以做到持久化每一條 Redis 命令,但這對效能影響會很大,一般不會採用,如果不採用這種方式,在節點掛的時候肯定會損失小部分的資料,可能我們的鎖就在其中。

另一個方案是延遲啟動。就是一個節點掛了修復後,不立即加入,而是等待一段時間再加入,等待時間要大於宕機那一刻所有鎖的最大 TTL。

但這個方案依然不能解決問題,如果在上述步驟 3 中 B 和 C 都掛了呢,那麼只剩 A、D、E 三個節點,從 D 和 E 獲取鎖成功就可以了,還是會出問題。

那麼只能增加 Master 節點的總量,緩解這個問題了。增加 Master 節點會提高穩定性,但是也增加了成本,需要在兩者之間權衡。

任務執行時間超過鎖的 TTL

之前產線上出現過因為網路延遲導致任務的執行時間遠超預期,鎖過期,被多個執行緒執行的情況。

這個問題是所有分散式鎖都要面臨的問題,包括基於 Zookeeper 和 DB 實現的分散式鎖,這是鎖過期了和 Client 不知道鎖過期了之間的矛盾。

在加鎖的時候,我們一般都會給一個鎖的 TTL,這是為了防止加鎖後 Client 宕機,鎖無法被釋放的問題。

但是所有這種姿勢的用法都會面臨同一個問題,就是沒法保證 Client 的執行時間一定小於鎖的 TTL。

雖然大多數程式設計師都會樂觀的認為這種情況不可能發生,我也曾經這麼認為,直到被現實一次又一次的打臉。

Martin Kleppmann 也質疑過這一點,這裡直接用他的圖:

  • Client1 獲取到鎖。

  • Client1 開始任務,然後發生了 STW 的 GC,時間超過了鎖的過期時間。

  • Client2 獲取到鎖,開始了任務。

  • Client1 的 GC 結束,繼續任務,這個時候 Client1 和 Client2 都認為自己獲取了鎖,都會處理任務,從而發生錯誤。

Martin Kleppmann 舉的是 GC 的例子,我碰到的是網路延遲的情況。不管是哪種情況,不可否認的是這種情況無法避免,一旦出現很容易懵逼。

如何解決呢?一種解決方案是不設定 TTL,而是在獲取鎖成功後,給鎖加一個 watchdog,watchdog 會起一個定時任務,在鎖沒有被釋放且快要過期的時候會續期。

最後

文章中涉及到的知識點我都已經整理成了資料,錄製了視訊供大家下載學習,免費分享,誠意滿滿,希望可以幫助在這個行業發展的朋友,在論壇部落格等地方少花些時間找資料,把有限的時間,真正花在學習上,所以我把這些資料,分享出來。相信對於已經工作和遇到技術瓶頸的朋友們,在這份資料中一定都有你需要的內容。

資料免費獲取方式:點選這裡免費領取Dubbo、Redis、Netty、zookeeper、Spring cloud、分散式、高併發等架構技術資料