分布式鎖實踐中的一些坑及優化手段

2021-10-21 17:09:35 字數 1902 閱讀 2731

本文僅討論使用redis作為分布式鎖,不考慮redis其他一些問題,如是否為單機節點,怎麼做主從集群或者哨兵,與zookeeper和其他分布式共識演算法實現的分布式中介軟體有什麼區別,我選擇redis主要還是考慮它自身的效能和編碼的複雜度,至於cp還是ap的選型暫時不作為考量標準。

當微服務由單機部署變為分布式集群部署,在業務中涉及的一些資料庫操作或者其他可能存在併發問題的地方,都有可能因為**層面考慮不周或存在漏洞,導致資料丟失更新,資料不一致的問題發生,我也是在工作中遇到這個問題。例如a請求查詢資料庫中可用次數為100,此時b請求也查詢資料庫中可用次數為100,a請求進行-1之後在資料庫中修改為99,而b請求也-1之後修改資料庫中可用次數為99,這時就發生了資料丟失更新的問題,給公司造成了損失,當併發量更大,b請求出現一定延時,可能會發生其他請求已經修改了十多次,而b請求又將可用次數改為99,如此日積月累會造成巨大的損失。

在根據一些技術文件提供的解決方案中,我選擇了redis來實現分布式鎖,大致架構如下

實現大致原理:

當請求過來時,通過lua指令碼,使用空間名稱key和請求id作為value,在redis中存入乙個過期時間為10s的str

請求處理完畢後主動釋放自己加的鎖,如果節點失敗也可以通過過期時間自動刪除,不會出現「死鎖」

當多個請求同時發生,先競爭到鎖的先執行,沒有搶到鎖的則生成乙個隨機等待時間,等待結束後再發起競爭,這樣可以防止大量請求同時競爭造成羊群效應

如上架構,確實可以直接解決資料丟失更新的問題,這樣意味著所有節點上的所有請求在操作的時候都會去加鎖,讀與讀,讀與寫直接加鎖,如此一把大鎖懸在空中,勒住了所有請求,系統效能直線性下降,併發越大競爭越大。

併發三組執行緒請求三個不同的介面,執行緒數100,ramp為1秒,迴圈2次,傳送等待鎖次數57720

併發四組執行緒請求四個不同的介面,執行緒數100,ramp為1秒,迴圈2次,傳送等待鎖次數74180

cpu佔用率

根據測試可以得出以下分析

加鎖可以解決資料丟失的問題

加鎖會導致併發量急劇下降,最大為7/s

加鎖導致大量鎖競爭,至高併發時,單個請求需要競爭近百次

目前的鎖很明顯是悲觀鎖,請求上來就會先加鎖,可以在優化為樂觀鎖,先去判斷當前是否有請求衝突發生,如果沒有競爭,則直接操作,如果有則進行加鎖,類似於cas這樣的機制,可以少去首次加鎖的開銷。

細化鎖粒度,參考innodb的行鎖設計,這個需要根據具體的業務場景來考量,在我們的業務中,同一使用者的同一請求可以根據索引或鍵值id來確定,然後用這類主鍵id作為鎖空間命名,可以極大程度的細化鎖,不會讓不同使用者的不同請求發生衝突。

縮小隨機等待時間區間,當競爭鎖失敗後,隨機等待多久再去發起競爭呢,等待時間太長沒有必要,等待時間過短的話會加劇競爭失敗的發生。最初我的值是50-200ms,後來進過jmeter一系列壓測,確定在10-50ms這個區間競爭數少,等待時間也短。

從sql的角度來看,能否將這樣類似的計費等操作進行合併,不要在**中取出資料再進行增減,這樣可以直接利用mysql中innodb的行鎖,減少業務層對請求的阻塞。

公升級優化的方案有很多,還可以使用第三方分布式鎖的框架,提供一些可重入鎖的支援等,具體場景具體優化,但網上提供的解決辦法不能生搬硬套,否則在帶來**侵入的同時還無法提高效能。

關於分布式鎖的一些總結

為了防止分布式系統中的多個程序之間相互干擾,我們需要一種分布式協調技術來對這些程序進行排程。而這個分布式協調技術的核心就是來實現這個分布式鎖。分布式鎖實現的三個核心要素 加鎖 解鎖 鎖超時 1 加鎖 最簡單的方法是使用setnx命令。key是鎖的唯一標識,按業務來決定命名。比如想要給一種商品的秒殺活...

分布式的一些思考

最有效率的分布式是在執行方法前知道所執行的方法使用的資料即所謂環境,並把相關資料和方法本法放到指定的機器上執行,返回結果給指定的客戶端。在方法本身不確定的前提下,所有資料都是環境一部分。如果使用統一資料伺服器的方法,網路和硬碟的開銷抵消了分布式的優勢。因為大部分操作無外乎就是把資料簡單操作後放到新的...

我在分布式session上的一些實踐

這篇文章大致講解了用nginx tomcat spring redis實現分布式session。由於nginx的多程序模式以及事件驅動,它作為乙個web伺服器的效能是相當好的。至於怎麼用它來做反向 只需要很簡單的配置nginx.conf檔案的以下部分並且重啟nginx即可 server ip1 po...