點選上方“芋道原始碼”,選擇“置頂公眾號”
技術文章第一時間送達!
原始碼精品專欄
-
1. 概述
-
2. 讀寫鎖
-
666. 彩蛋
1. 概述
本文主要分享 Eureka 註冊中心的那把讀寫鎖,讓我瘙癢難耐,卻不得其解。在某次意外的摳腳的一刻( 筆者不抽煙,如果抽煙的話,此處應該就不是摳腳了 ),突然頓悟,爽,這好比… 比喻有點猥瑣,筆者就省略 100 字。
不瞎比比,上程式碼:
public abstract class AbstractInstanceRegistry implements InstanceRegistry {
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
private final Lock write = readWriteLock.writeLock();
// ... 省略其他程式碼
}
推薦 Spring Cloud 書籍:
-
請支援正版。下載盜版,等於主動編寫低階 BUG 。
-
程式猿DD —— 《Spring Cloud微服務實戰》
-
周立 —— 《Spring Cloud與Docker微服務架構實戰》
-
兩書齊買,京東包郵。
推薦 Spring Cloud 影片:
-
Java 微服務實踐 – Spring Boot
-
Java 微服務實踐 – Spring Cloud
-
Java 微服務實踐 – Spring Boot / Spring Cloud
2. 讀寫鎖
我們把設計到讀寫鎖的方法整理如下:
方法 | 讀鎖 | 寫鎖 | 不使用 |
---|---|---|---|
#register(...) |
√ | ||
#cancel(...) |
√ | ||
#evict(...) |
√ | ||
#renew(...) |
√ | ||
#statusUpdate(...) |
√ | ||
#deleteStatusOverride(...) |
√ | ||
#getApplicationDeltasFromMultipleRegions(...) |
√ | ||
#getApplicationsFromMultipleRegions(...) |
√ |
是否看到這讀寫感到幾絲詭異的味道?OK,我們把問題梳理如下:
-
A. 為什麼
#register(…)
/#cancel(…)
/#evict(…)
/#statusUpdate(…)
/#deleteStatusOverride(…)
等寫操作使用讀鎖 -
B. 為什麼
#renew(…)
寫操作不使用鎖 -
C. 為什麼
#getApplicationDeltasFromMultipleRegions(…)
讀操作使用寫鎖 -
D. 為什麼
getApplicationsFromMultipleRegions(…)
讀操作不使用鎖
先解釋 A + C :
我們來回想下,在 Eureka 應用集合一致性雜湊碼的公式:appsHashCode = ${status}_${count}_
。( 不瞭解的同學可以載入下 《Eureka 原始碼解析 —— 應用實體註冊發現(七)之增量獲取》「 2. 應用集合一致性雜湊碼 」 )
應用實體的數量和狀態都會影響雜湊碼的計算結果。也就是說,上述前六個( 包括不使用鎖的 #renew(...)
方法 )方法的呼叫都會影響雜湊碼。
我們把目光移向唯一使用寫鎖的 #getApplicationDeltasFromMultipleRegions(...)
方法,該方法執行過程中,需要保證 recentlyChangedQueue
和 registry
共享變數的應用實體的狀態一致,不然傳回的增量應用實體集合的狀態是不準確的。此時能夠達到該效果,必須讓 #getApplicationDeltasFromMultipleRegions(...)
和前六個方法互斥。方案如下:
-
a. 全部
synchronized
-
b.
#getApplicationDeltasFromMultipleRegions(…)
使用讀鎖,前六個方法使用寫鎖 -
c.
#getApplicationDeltasFromMultipleRegions(…)
使用寫鎖,前六個方法使用讀鎖
Eureka 選擇了方案c,原因如下:
-
a. 效能太差
-
b. 前六個方法使用寫鎖,勢必衝突太大,雖然讀肯定比寫多。
-
c.
#getApplicationDeltasFromMultipleRegions(…)
使用寫鎖,配合 ResponseCache ,即減少了寫鎖使用的頻率,每次快取過期才使用,又避免了前六個方法因為方案b中的寫鎖導致互斥。( 不瞭解 ResponseCache 的同學可以載入下 《Eureka 原始碼解析 —— 應用實體註冊發現(六)之全量獲取》「 3.2 響應快取 ResponseCache 」 )
再解釋 D
#getApplicationsFromMultipleRegions(...)
方法的邏輯,只依賴 registry
共享變數,不存在應用實體的狀態一致的困擾,所以不使用鎖。
最後解釋 B
#renew(...)
方法的邏輯,雖然會影響應用實體的狀態,但是是極小機率,考慮到它呼叫的比較頻繁,比起因為鎖給這個方法帶來的效能降低,不如傳回的結果暫時不夠準確。( 想瞭解極小機率發生原因的同學可以載入 《Eureka 原始碼解析 —— 應用實體註冊發現(八)之改寫狀態》「 4.3 續租場景 」 )
TODO [0029] 讀寫鎖
筆者路上突然又想了問題,可能不是上述原因,可能和 ResponseCache 有關係,參見 #invalidateCache(...)
方法的每次呼叫。也就是說,這個讀寫鎖是針對 ResponseCache 的讀寫鎖。
666. 彩蛋
開森 !
本來以為需要跟 Eureka 官方提交 issue 提問,並且做好了獲得不到答案的準備,結果無意中的摳腳( 請允許我熱愛摳腳給我帶來的靈感 )解答了自己的疑惑。
歲月是把糾結而又萌萌噠的鎖,你不知道你的困擾,哪天不經意的被開啟。
願大喜大悲,不枉僅知的這一生。
如果你對 Dubbo 感興趣,歡迎加入我的知識星球一起交流。
目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:
01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽
05. 拓展機制 SPI
06. 執行緒池
07. 服務暴露 Export
08. 服務取用 Refer
09. 註冊中心 Registry
10. 動態編譯 Compile
11. 動態代理 Proxy
12. 服務呼叫 Invoke
13. 呼叫特性
14. 過濾器 Filter
15. NIO 伺服器
16. P2P 伺服器
17. HTTP 伺服器
18. 序列化 Serialization
19. 叢集容錯 Cluster
20. 優雅停機
21. 日誌適配
22. 狀態檢查
23. 監控中心 Monitor
24. 管理中心 Admin
25. 運維命令 QOS
26. 鏈路追蹤 Tracing
…
一共 60 篇++