面試官:高并發(fā)下重啟服務(wù),接口調(diào)用老是超時,你有什么解決辦法?
Hello,大家好,我是樓下小黑哥~
今天文章內(nèi)容來自一位朋友出去面試碰到的問題:
「了解 Dubbo 服務(wù)預(yù)熱過程嗎?詳細聊聊它的原理。」
這個問題朋友沒有很好答出來,因為之前也沒了解過。說實話一開始我只是大概知道這塊預(yù)熱的代碼位于何處,但是原理什么的還是沒有仔細去了解。
所以這次仔細去看了下代碼,查了一些 Github 這塊代碼提交記錄,終于搞明白這塊的原理,跟大家一起分享下。
預(yù)熱
首先我們來看下什么是服務(wù)預(yù)熱?
先舉一個生活的中的例子,買過新車的同學應(yīng)該知道新車都有一個磨合期的,大概開個一兩千公里之后,才能達到最佳的狀態(tài)。
其實服務(wù)預(yù)熱也是這個意思,服務(wù)剛啟動的時候?qū)⒋嬖谝欢巍改ズ掀凇梗@段期間服務(wù)運行狀態(tài)沒有達到最佳,如果一下子將服務(wù)流量提升到平常的狀態(tài),可能會存在大量的請求超時或者瞬間將系統(tǒng)壓垮。
所以服務(wù)剛啟動的時候我們要慢慢增加的流量,直到一段時間后增加到閾值的上限,給系統(tǒng)一個「預(yù)熱過程」,讓其運行狀態(tài)到達最佳。
那為什么服務(wù)剛啟動的時候系統(tǒng)狀態(tài)沒有到達最佳狀態(tài)?
大概原因其實如下:
Java 應(yīng)用存在一個類加載的過程,而這個過程是按需加載的。即服務(wù)剛啟動時候,JVM 只加載了啟動過程必需的類。
我們自己所需要的類,直到服務(wù)被調(diào)用之后才會被真正的加載。
另外對于一些「熱點代碼」,JVM 將會使用 JIT 編譯器編譯成本地代碼,提高運行速度。
上面兩個過程是出于 JVM 系統(tǒng)層面的影響。
除此之外,我們服務(wù)系統(tǒng)中可能會需要一些緩存資源。剛啟動的時候,由于資源不存在,服務(wù)需要去加載這些資源。
Dubbo 預(yù)熱實現(xiàn)方式
好了,了解完預(yù)熱是咋回事后,我們回到正題,來看下 Dubbo 是如何實現(xiàn)預(yù)熱的。
首先我們來看下 Dubbo 服務(wù)模型:
服務(wù)提供者啟動之后將會把節(jié)點相關(guān)信息注冊到注冊中心,服務(wù)消費者通過注冊中心就可以及時獲取所有的服務(wù)節(jié)點。
當服務(wù)消費者調(diào)用服務(wù)時,內(nèi)部將會通過負載均衡組件選擇一個節(jié)點,進行服務(wù)調(diào)用。
如上圖所示,假設(shè) B 節(jié)點服務(wù)剛啟動,其需要一個預(yù)熱過程,這就需要服務(wù)消費者逐漸將流量分發(fā)給 B 節(jié)點。
下面我們就從 Dubbo 源碼出發(fā),觀察服務(wù)預(yù)熱具體實現(xiàn)方式,具體源碼位于 AbstractLoadBalance#getWeight
ps: 當前源碼 Dubbo 版本為2.7.4,低于這個版本代碼實現(xiàn)存在少量差異,詳情見下文。
這段代碼主要分為三步:
- 獲取服務(wù)提供者啟動時間 timestamp
- 使用當前時間減去服務(wù)提供者啟動時間,計算服務(wù)提供者已運行時間 uptime
- 根據(jù)已運行時間動態(tài)計算服務(wù)預(yù)熱過程的權(quán)重
第三步動態(tài)權(quán)重計算方法如下:
這里計算方式其實很簡單,簡單來說服務(wù)運行時間越久,權(quán)重越高,直到正常權(quán)重。
假如服務(wù)提供者已運行 1 分鐘,那么 weight 最終結(jié)果為 10 。
假如服務(wù)提供者已運行 5 分鐘,那么 weight 最終結(jié)果為 50 。
假如服務(wù)提供者已運行 11 分鐘,超過默認預(yù)熱時間的閾值 10分 鐘,那么將不會再計算,直接返回 weight 默認權(quán)重。
這里我們需要注意的是,Dubbo 默認提供五種負載均衡的策略:
- Random LoadBalance :「加權(quán)隨機」策略
- RoundRobin LoadBalance:「加權(quán)輪詢」策略
- LeastActive LoadBalance:「最少活躍調(diào)用數(shù)」策略
- ConsistentHash LoadBalance:「一致性 Hash」 策略
- ShortestResponse LoadBalance:「最短響應(yīng)時間」策略
「ShortestResponse LoadBalance」 策略小伙伴們可能會比較陌生,官方文檔中并沒有提到這個策略。
其實這個是 Dubbo 2.7.7 版本新增的負載均衡策略,官方文檔估計還沒更新。
ps:感興趣的小伙伴,可以去修改下官方的文檔,增加這個新的負載均衡的策略,為開源獻出我們的一份力量。
回到正文,從AbstractLoadBalance#getWeight調(diào)用關(guān)系可以看到,「ConsistentHash LoadBalance」 實現(xiàn)類是不支持服務(wù)預(yù)熱,這點需要注意一下。
Dubbo 預(yù)熱歷史 bug-反復橫跳雖然 Dubbo 預(yù)熱的相關(guān)代碼,總體看起來不是很難,但是歷史版本還是存在幾個 Bug,導致預(yù)熱失效。
Dubbo 2.5.5 之前的版本
在 Dubbo 2.5.5 之前的版本,AbstractLoadBalance#getWeight實現(xiàn)方式如下:
這個版本跟現(xiàn)在代碼一樣,都是從節(jié)點的 timestamp獲取服務(wù)啟動時間。不過這個版本存在一些問題,Dubbo 沒有把服務(wù)提供者啟動時間傳給消費者,導致這里獲取 timestamp是消費者啟動時間,這樣就導致預(yù)熱失效。
等到 Dubbo 2.5.6 ,修復這個問題,源碼如下:
這個版本將服務(wù)提供者啟動時間單獨保存在 remote.timestamp 屬性中,源碼位于 ClusterUtils#mergeUrl
通過這種方式修復預(yù)熱失效的問題。
Dubbo 2.7.2 預(yù)熱又失效了
當 Dubbo 版本升級到 2.7.2 ,這個預(yù)熱失效 Bug 又回來了。帶來這個問題主要原因是ClusterUtils#mergeUrl 源碼中清除了remote.timestamp,轉(zhuǎn)而統(tǒng)一使用 timestamp保存服務(wù)啟動時間。
但是呢,由于修改沒有徹底, AbstractLoadBalance#getWeight還是依然使用 remote.timestamp 獲取服務(wù)啟動時間,這就導致預(yù)熱失效。
預(yù)熱代碼的隱藏 bug
這個 Bug 在Dubbo 2.7.4 版本被徹底修復,另外還順帶優(yōu)化了代碼中存在缺陷。
先看下原先的代碼中國缺陷,原先預(yù)熱代碼實現(xiàn)采用如下方式計算服務(wù)啟動運行的時間。
int uptime = (int) (System.currentTimeMillis() - timestamp);
但是這里存在一個問題,如果服務(wù)提供者與消費者兩端時鐘是不一致,服務(wù)提供者啟動時間很有可能會大于消費者本地時間。
這種情況,uptime 計算結(jié)果為一個負值,這就會導致權(quán)重將使用配置的默認值,預(yù)熱也失效了。
所以針對這種情況 「@aftersss」 提供了修復的方案,加入相關(guān)的判斷,當 uptime為負值的時候,直接返回權(quán)重 1。
不過在 「Code review」 過程中,「@beiwei30」 覺得不用加入額外 if 判斷,可以直接使用 Math.max兼容。
不過這樣修改,還是存在一個問題:Integer 精度丟失問題。
如果此時 System.currentTimeMillis() = 1566209746000(2019-08-19 18:15:46),而 timestamp = 1561914711000(2019-07-01 01:11:51),當兩者差值為:「4295035000」。
這是一個遠大于 Integer.MAX_VALUE的值,所以在 long 轉(zhuǎn)為 int 時候精度丟失,導致最后實際得到 int 值為 「67704」。
而這個值小于服務(wù)預(yù)熱的默認時間(10 * 60 * 1000),所以進入動態(tài)計算權(quán)重環(huán)節(jié),最終將得到一個比較小的權(quán)重,這就導致「假預(yù)熱」。
所以最后還是采用 「@aftersss」 修復的方案,采用 long 類型存儲時間戳計算結(jié)果,最終優(yōu)化代碼如下:
總結(jié)
今天的文章主要介紹了服務(wù)預(yù)熱的作用,以及 Dubbo 服務(wù)預(yù)熱的實現(xiàn)方式。
這個實現(xiàn)方式整體來說不是很難,簡單來說就是隨著運行時間逐漸提高權(quán)重,從而增加服務(wù)節(jié)點的流量。
如果你當前使用框架并沒有這個功能,而你正需要服務(wù)預(yù)熱,可以參考 Dubbo 的實現(xiàn)方式。
另外由于 Dubbo 歷史版本存在一些 Bug,如果各小伙伴需要使用服務(wù)預(yù)熱功能,需要注意避免使用以下版本:
- 「Dubbo 2.5.5 之前的版本」
- 「Dubbo 2.7.2/ 2.7.3」
好了,今天的文章就到這里了~我是樓下小黑哥,你知道的越多,你不知道的就越多。
我們下周再見~
相關(guān)資料https://github.com/apache/dubbo/issues/6242
https://github.com/apache/dubbo/issues/306
https://github.com/apache/dubbo/pull/4870
本文轉(zhuǎn)載自微信公眾號「程序通事」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系程序通事眾號。