社群文章|MOSN 構建 Subset 優化思路分享

語言: CN / TW / HK

圖片

文|李旭東(花名:向旭 )

螞蟻集團技術專家

螞蟻中介軟體研發,專注於 SOFARegistry 及其周邊基礎設施的開發與優化

本文 2098 字 閱讀 8 分鐘

|前言|

MOSN 使用了 Subset 演算法作為其標籤匹配路由負載均衡的方式。本文主要介紹 Subset 的原理,包括了在超大規模叢集下 MOSN 的 Subset 所遇到的一些效能瓶頸與採用的優化演算法。

首先,為什麼要優化 Subset 呢?

總體而言,效能瓶頸往往會由於叢集規模的增大逐漸暴露出來。在螞蟻的超大規模的叢集上,註冊中心推送地址列表會對應用造成一定的開銷。

在我所參與過的某一次大規模壓測中,核心應用的機器數目非常龐大,當進行釋出或者運維的時候,它的地址列表會被推送給所有呼叫它的應用。

而 MOSN 會接收這份地址列表重新構建自己的路由。當地址列表非常龐大的時候,MOSN 更新 cluster 的效能瓶頸逐漸地暴露出來,出現了較高的 CPU 毛刺,記憶體在短時間內出現了上漲,gc 頻率也大幅度增加。

通過下方的火焰圖,我們可以看到這次壓測期間對某應用的 MOSN 的 pprof:

- Alloc:

image.png

- CPU:

圖片

從 pprof 可以看到,無論是 CPU 還是 alloc 的開銷, 構建 SubsetLoadBalancer 都明顯佔了大頭,所以優化這部分的構建是亟待去做的一件事。

最終通過探索優化,我們可以減少 SubsetLoadBalancer 構建過程中 95% 的 CPU 開銷和 75% 的 alloc 開銷。

下面讓我們一起回顧下本次優化的過程與思路。

PART. 1--Subset 基本原理介紹

在一個叢集裡,通常機器會有不同的標籤,那麼如何將一個請求路由到指定標籤的一組機器呢?

MOSN 的做法是把一個服務下的機器按照機標籤組合進行預先分組,形成多個子集。在請求的時候,根據請求中的 metadata 資訊可以快速查詢到這個請求對應應該匹配到的子集。

如下圖所示,可以看到當前有 4 個節點:

圖片

標籤匹配規則會根據 zone 、mosn_aig 、mosn_version 這 3 個欄位進行匹配路由,根據這 3 個 key 的排序進行組合得到以下匹配路徑:

圖片

相對應的匹配樹如下:

圖片

假設需要訪問 {zone: zone1, mosn_aig: aig1},那麼經過排序後查詢順序為 mosn_aig:aig1 -> zone:zone1,查詢到 [h1, h2]。

以上就是 Subset 的基本原理介紹。

PART. 2--MOSN 對 Subset 的構建

首先需要輸入的引數有兩個:

- 帶標籤的機器列表 hosts,比如 [h1, h2, h3, h4];

- 用於匹配的 subSetKeys, 如下圖:

圖片

接著我們先帶上思路,然後閱讀原始碼來看一下 MOSN 的 SubsetLoadBalancer 是如何構建這棵樹的。

核心思路如下:

- 遍歷每一個 host 的 labels 和 subSetKeys 遞迴去建立一棵樹;

- 對於樹的每一個節點,都會遍歷一次 hosts 列表,過濾出匹配這個節點的 kvs 的 subHosts,每個節點建立一個子 load balancer。

我們來看原始碼圖:

圖片

整體的構建複雜度是 O (MNK) (M: Subset 樹節點個數,N: Hosts 個數, K: 匹配的 Keys)

PART. 3--構建效能瓶頸分析

通過對生產的 profile 分析,我們發現 SubsetLoadBalancer 的 createSubsets 在 CPU 和 alloc 的火焰圖中的佔比都較高。所以下面我們開始編寫 benchmark,來優化這一部分的效能。

我們的輸入引數為:

- subSetKeys:

圖片

- 8000 個 hosts (每個 hosts 都有 4 個 label, 每個 label 對應 3 種 value)

圖片

接著,我們來看 CPU 和 alloc_space 的佔用情況。

- CPU:

圖片

- alloc_space:

圖片

從上面兩張火焰圖中,我們可以看出 HostMatches 和 setFinalHost 佔用了較多的 CPU_time  和 alloc_space。我們首先來看下 HostMatches:

圖片

圖片

它的作用是判斷一個 host 是不是完全匹配給定的鍵值對,且判斷這個 host 是否匹配這個匹配樹節點。

它的開銷主要在於執行次數過多:treeNodes * len(hosts) ,所以在叢集變大時,這邊的執行開銷會大幅度上升。

然後我們再來看一下 setFinalHost:

圖片

圖片

他的主要邏輯是按 IP 進行去重,同時會附帶 copy。如果我們在 SubsetLoadBalancer 的頂層進行去重,那麼它的任意 Subset 都不需要再次去重。因此,這邊可以把它改成不去重。

PART. 4--倒排索引優化構建

在 HostMatches 的這麼多次匹配中,實際上有很多的重複操作,比如對 host label 中某個 kv 判斷 equals,在構建過程中重複的次數相當之多。

所以優化的思路可以基於避免這部分重複的開銷,從預先構建倒排索引出發。具體步驟展開如下:

1. 輸入兩項引數:

- subSetKeys:

圖片

- hosts:

圖片

2. 遍歷一次 hosts,針對每個 kv 我們用 bitmap 構建倒排索引:

圖片

3. 根據 subSetKeys 和倒排索引中的 kvs,構建出匹配樹,因為索引中是去重的與 hosts 數目無關,這個操作開銷佔比很低;

4. 對於樹的每個節點,利用倒排索引中的 bitmap 做交集快速得到匹配全部 kv 的 hosts 的索引 bitmap;

5. 使用 bitmap 中儲存的 index 從 hosts 中取出對應 subHosts 構建子 load balancer,同時注意此處不需要使用 setFinalHosts 進行去重。

基於上述思路過程開發新的 Subset preIndex 構建演算法,可以在 MOSN 的對應 Pull Request 頁面檢視詳情:

https://github.com/mosn/mosn/pull/2010

再分享下新增 benchmark 進行測試的地址:

https://github.com/mosn/mosn/blob/b0da8a69137cea3a60cdc6dfc0784d29c4c2e25a/pkg/upstream/cluster/subset_loadbalancer_test.go#L891

圖片

圖片

可以看到相對之前的構建方式,構建速度快了 20 倍,alloc_space 減小了 75% 。同時,alloc 次數出現了少量的上升,這是因為需要額外的構建一次倒排索引所致。

下面觀察一下 gc:

圖片

我們可以發現,相較於之前的構建方式,執行期間的記憶體更小了,而且 CPU 回收的記憶體也變少了,同時 gc 並行掃描的時長小幅上漲,STW 時間變的更短。

最後,測試一下在不同 hosts 數目下的優化程度,可以看到在 hosts 數目較多時 (>100) , 新的構建演算法都會大幅的優於舊的構建演算法。

圖片

PART. 5--總結

我們看到在大規模生產環境中,一些以前不會注意到的效能瓶頸往往會暴露出來,但通過壓測,我們能提前發現並優化這些問題。

目前,該構建演算法已經合併到 MOSN master,作為 MOSN 預設的 SubsetLoadBalancer 構建方式。

在這次優化過程中,我們用到了一些常見的優化手段,如:倒排索引、bitmap。不難看出,這些優化手段雖然基礎常見, 但也取得了理想的優化效果,希望能對大家有所幫助。

瞭解更多

MOSN Star 一下✨:

https://github.com/mosn/mosn

本週推薦閱讀

MOSN 文件使用指南

圖片

MOSN 1.0 釋出,開啟新架構演進

圖片

MOSN Contributor 採訪|開源可以是做力所能及的事

圖片

【2022 開源之夏】SOFAStack 和 MOSN 社群專案中選結果

圖片

圖片