互聯網都在說降本增效,小紅書技術團隊是怎么做的?
隨著小紅書業務的快速發展,資源消耗和成本壓力顯著增加。在降本增效的大背景下,我們建設了性能持續優化 & 追蹤平臺,來系統性輔助業務團隊解決性能問題,在業務系統日常的演化過程中,持續跟進、追蹤系統的性能退化并推動優化。
目前,這一平臺已覆蓋小紅書搜索、推薦、廣告的 S0 服務,運行兩個多月以來,輔助業務團隊存量優化超1萬 CPU 核;發現性能退化超1萬 CPU 核并跟進優化。
1、背景
當前,小紅書正處在快速發展期間,流量的快速上漲和業務的快速迭代,顯著增加了資源消耗和成本壓力。
在存量的資源占用上,我們要求研發人員對應用做盡可能深度的性能優化。然而,研發人員在對自己的模塊做性能優化時,往往缺少工具來輔助分析,工具的合理選擇、環境配置、使用方式等各個方面,都有較高的學習成本。
另一方面,當前性能優化主要依靠個人經驗進行逐個分析,缺乏通用化的機制,經驗較難在團隊間共享。即使有人發現一個通用的性能問題,也很難衡量其涉及的模塊和整體的優化空間。
此外,小紅書業務的日常迭代往往會帶來增量的資源消耗,即性能退化。特別是對于頻繁迭代的模塊,如一些推薦應用,每天進行發版且每個版本涉及十多次不同的提交。在這些提交中,可能會隱藏著一些性能退化點。顯著的性能退化點會在性能壓測中被發現,而更多的性能退化往往是微小的,比如一個 commit 帶來了整體 CPU 1% 左右的占用,這樣微小的退化是隱蔽的,通過常規壓測等手段比較難以發現。
隨著業務的迭代,日積月累下,多個微小的性能退化會導致應用整體性能的顯著惡化,進而積重難返。經過一段時間的積累后,在排查這種問題時,面對動輒上百次的代碼提交歷史,開發同學很難排查出真正導致性能退化的提交是什么。最終只能以穩定性的名義增加資源擴容,這樣的情況多次發生。
針對此,我們嘗試從整體上解決性能問題,設計開發了一套性能優化和持續追蹤的平臺,來輔助應用研發人員分析性能問題,同時在日常的業務系統演化過程中,持續跟進、追蹤系統的性能退化并推動優化。
2、思路
我們的目標是從整體上解決性能問題。
問題主要聚焦在以下三個方面:
存量性能優化:對應用進行全方位的深入分析、診斷、優化;優化的經驗積累后,橫向擴展,做到通用的優化
增量性能退化攔截:業務迭代過程中,主動發現應用的增量資源占用,即性能退化
性能穩定性問題:對一些突發的性能惡化導致的穩定性問題,快速定位原因
對應的總體技術思路:
分析手段:基于 profiling 等采樣手段,來對應用進行剖析
產品化:做到平臺化來提高易用性,研發人員可以盡可能低門檻、高效的使用;
持續優化:在機制上做到持續優化,將采樣分析做到常態化、例行化,將性能優化、分析、防退化,融入到業務應用的日常迭代中,關注應用的每一個版本、每一次代碼提交、每一個策略實驗進行,持續追蹤業務應用的性能表現。
3、整體方案
我們的內部落地整體架構如下:
核心思路是通過對業務進程進行持續性的采樣、處理、存儲,并基于采樣數據做分析,來輔助性能優化和發現性能退化。
數據采樣上,我們用單機低頻次持續采樣,降低成本,減少對應用的影響。
在分析上,數據來自大數據存儲,并從縱向、橫向來對比分析:在縱向上,采用 merge + diff 分析,來發現退化點;在橫向上,提供跨應用的通用查詢能力,查詢、分析函數粒度的資源占用。
3.1 數據采集、處理、存儲
3.1.1. 數據采集
當前主要的數據采集方式是通過對進程進行 profiling,profiling 的原理是基于一定的頻率對運行進程進行采樣,來了解進程的特征。當前,profiling 支持從多個方面對程序進行采樣分析,如 CPU、Memory、Thread、Lock、I/O 等。日常使用中,對 CPU 進行 profiling 的應用最為廣泛。
一般的 CPU profiling 是 On-CPU,也就是 CPU 時間花費在哪些代碼執行上。在 On-CPU 之外,還有 Off-CPU,指的是進程不在 CPU 上運行運行的時間,比如進程因為 IO、鎖等原因處于等待,花費了時間。所以,Off-CPU 是對 On-CPU 的補充,整體關系如下:
根據應用的實際情況,綜合的對 On-CPU、Off-CPU 進行采樣,更為全面地了解程序的運行情況。
在多語言支持方面,當前我們支持 C++、JAVA、Golang 等主流語言:
C++ 應用:通過 Linux perf
JAVA 應用:目前主流方案(如 IntelliJ IDEA 和阿里的 Arthas)是通過 Async-profiler 來實現 profiling。Async-profiler 是將 Perf 的堆棧追蹤和 JDK 提供的 AsyncGetCallTrace 結合了起來,低開銷的支持多種 Perf event。我們的方案里也借助了這個工具,并針對我們的需求進一步做了定制開發
Golang 應用:通過 Golang 內置的 pprof
在采樣形式上,我們支持定時、主動和條件觸發三大形式。當前,小紅書的線上應用基本開啟了常態化、定時的 profiling
3.1.2 業務接入
采樣的 agent 以 daemonset 方式部署,支持對物理機上的多個業務 pod 進行采樣。對應用的采樣開啟、關閉是通過配置中心來下發。此外,支持更多的采樣配置,如:單次采樣的采樣頻率、采樣時間配置;多次采樣之間的采樣周期;采樣方式切換等。
因此,我們當前做到了業務無感知接入,接入在分鐘級別生效。
3.1.3 存儲
在采樣結束后,對采樣后的數據進行解析、處理,如根據函數調用鏈統計 sample 數、過濾占比過低的函數調用鏈等。處理后,我們將數據進行存儲,用于后續的分析。我們的存儲方案選擇的是 clickhouse,在存儲 profiling 的數據之外,同時會把相關的環境變量信息一起存儲,如應用名、應用版本、機房等。此外,采樣后生成單 Pod 的火焰圖,將火焰圖壓縮并保存在對象存儲中,如騰訊云 cos。
3.1.4 資源消耗
在成本上,單次采樣的持續時間一般不超過一分鐘,多次采樣之間的周期間隔是小時級別,因此對應用程序基本沒有影響;單次 pod 單次采樣,經處理并保存到 clickhouse 的數據在千行的規模。所以整體的項目成本基本是存儲成本,即 clickhouse 和對象存儲,都很便宜,整體近乎零成本。
3.2 存量優化
3.2.1 目標
根據我們的觀察和經驗,一線研發同學對生產服務的診斷分析訴求長期是被壓制的,主要原因在:
· 公司出于安全需要,會對生產環境的網絡和權限進行管控。
這導致一些診斷會非常麻煩,例如,小紅書有一些系統采用了超大 Java Heap,如果要對此應用做堆分析需要的步驟:
- dump 堆到本機指定為止;
- 傳輸 hprof 文件到指定跳板機;
- 從跳板機下載 hprof 文件到本地;
- 本地需要花數小時對 hprof 文件建索引,對于幾十 GB 的堆,本地往往由于機器性能不夠,最終可能還是無法完成分析;
· 通過一些診斷工具,我們可以觀測到很多系統運行指標,但對指標的解讀往往需要很多經驗和對業務的理解。
如運行 free 命令,我們可以得到系統的內存使用指標(free/buffer/cache)。然而這些指標到底意味著什么?對一個特定的應用,當前水位是否合理?這對使用者是有較高的基礎和業務背景知識要求。
由于這些限制和不便,使得我們一線資深研發同學日常對性能的關注逐漸變少,而一些新同學更是望而卻步。最終系統由于缺乏“體檢”,既不能治于未?。挥忠蛉狈ο到y的足夠認知,導致需要治療時又無從下手。
為此,我們設定了一個小目標:把診斷變成一個日常觸手可及的事:
· 開箱即用:
我們將一些常用的工具打包成一個工具箱,一鍵(或默認)安裝到目標容器里;
· 白屏化:
所有操作都在網頁上通過點擊拖拽完成,研發同學不需要記住很多命令參數,同時對工具的輸出做解析和解釋;
· 知識庫:
縱向上,我們會積累歷史指標供參考。橫向上,我們會總結一些共性的優化點供研發同學參考。
3.2.2 工具
3.2.2.1 基礎信息展示
這部分主要展示一些進程和環境相關的基礎信息,OS、JVM、機器配置、啟動參數、環境變量等。方便用戶迅速了解一些應用的基本信息。
3.2.2.2 運行時指標
這塊主要涵蓋一些秒級運行時的 Metrics(如 CPU 利用率,GC 信息),loaded class,線程池狀態等。這塊作為大盤指標的補充,在 agent 測內嵌了一個小型的時序數據庫,直接在端上存儲幾個重要的秒級指標。幫助用戶捕捉一些更細粒度的信號。
3.2.2.3 采樣&分析
目前我們主要提供了針對 Java 的一些在線分析能力。對于 Java 程序,目前使用頻率最高的是堆分析,用戶可以在平臺上一鍵觸發 Heap dump,dump 文件生成后會自動上傳到內部部署的 apache-jifa worker 上,用戶可以在列表里面找到對應的入口,跳轉到 jifa 頁面去做詳細的堆分析。
同時,基于 profiling 工具,如 async-profler,用戶可以一鍵生成 cpu、alloc 及 lock 的火焰圖,并在線展示。通過這些火焰圖,能很方便找到系統的一些熱點,從而有針對性的去優化。
3.2.3 通用優化機制
在存量優化方面,我們的一個創新點就是通用優化機制。在對各應用進行常態化的 profiling 后,我們有了所有應用的性能原始數據,進一步的想法就是大數據檢索:經過眾多的性能優化 case 后,將常見的基礎庫和已知的通用性能問題抽象成規則庫,從而可以匹配其在線上所有模塊的消耗占比和整體占用核數,來發現更多優化空間,達到批量優化并且追蹤優化的效果。
下圖所示,為線上一個基礎庫 SDK 在各個應用中的資源消耗情況,分別統計了 CPU 占比和對應的核數。在此基礎上,批量的推動應用進行優化。
3.3 性能持續優化
3.3.1 總體思路
針對業務迭代過程中發生的性能退化,持續跟進、追蹤。
總體的思路是:首先是發現性能退化點,精確到函數級別;并進一步關聯、發現對應的變更事件(代碼提交、算法實驗等);后續跟進整個性能退化的生命周期,推動優化,直到最終解決性能退化。
3.3.2 發現
3.3.2.1 機制
首先是自動化巡檢,即每天會定期檢查接入的服務是否存在潛在性能退化,通過昨天和前天晚高峰性能數據,檢查各應用是否有性能退化情況,并推送到相關企微群。
此外,通過接入 QA 流水線、上線平臺等方式,在上線前、上線后回調,更早期攔截性能退化。
3.3.2.2 發現方式
通過性能數據 merge + diff 分析:根據查詢條件,在將新版本和基準版本分別進行數據聚合后,進行對比;通過分析對比后的 diff,發現異常變化點并判斷是否有性能退(精確到函數)。當前支持機房、版本和時間區間等多種條件。
此外,為了衡量性能退化點的影響,將退化的程度與對應占用的 CPU 核數相關聯,也可以讓研發人員們了解對應退化點對于系統整體性能的影響,有更直觀的感受。
3.3.2.3. 退化點展示
火焰圖是一種比較理想的展示函數調用關系的形式,同時也可以方便的定位其在整體中的位置。因此,我們通過定制化的差分火焰圖,展示退化點對應的詳細函數棧情況,并用顏色來標記、突出性能退化點,用不同的顏色來區分退化的程度;同時在火焰圖上展示對應的 CPU 核數,來強化退化程度,增加火焰圖的表達內容。
此外,為了支持版本間消失的代碼邏輯,使用了消失火焰圖。這樣,組合起來,可以展示兩個版本之間函數棧的新增、修改、消失等場景。
在技術實現上,我們在開源的 flamescope(https://github.com/Netflix/flamescope)基礎上定制開發,進行實時進行處理和渲染,根據需求可以靈活的支持各種應用場景。所有的火焰圖和 diff 計算均從 clickhouse 中讀取數據處理。
線上的一個實際性能退化例子如下,其中差分火焰圖中展示了退化點對應的函數調用、退化對應的 CPU 核數;消失火焰圖展示了版本之間消失的代碼邏輯。
3.3.3 定位變更
在確定性能退化后,根據性能退化的情況(如退化的時間點、函數棧),檢索應用對應的變更事件,如算法實驗變更、配置中心下發變更、上線記錄等。未來,會進一步嘗試根據函數棧來管理 git 的提交情況,關聯可能代碼提交。
3.3.4 持續追蹤
為了方便追蹤性能退化問題的進度,我們會把核實過的信息推送至內部風險平臺來錄入留痕,并且通過趨勢圖追蹤優化情況。
如上圖所示,這是一個線上應用性能退化的實際 case,通過函數調用鏈的 CPU 使用率趨勢圖可以看出性能退化發生的起始點,同時可以看出該性能退化是否得到了修復、何時修復,這樣可以清晰的看到性能退化問題的過程,方便持續追蹤性能問題。
3.4 性能異常問題定位
在采樣的方式上,支持條件觸發方式,即配置描述異常態的觸發條件(比如 CPU 突漲等),當滿足條件時進行數據的采集和上報。再基于上述的 merge + diff 數據分析方法,將異常態和正常態的數據分別進行匯聚后,做對比分析,通過 diff 分析來定位出導致突漲的根因,同時關聯對應的變更。
4、展望
未來,我們希望能夠去探索更多的性能優化手段,如 PGO;以及基于 PMU 指標,探索“內存大頁”等技術落地;同時,我們也希望能夠收集更多的性能指標,如 walltime、cpu cache、mem bindwith 等,來覆蓋更多的性能分析場景。
5、作者簡介
韓柏:技術部/可觀測技術組
小紅書可觀測技術工程師,畢業于上海交通大學,從事推薦架構、基礎架構工作,在可觀測、云原生、中間件、性能優化等方面有較為豐富的經驗。
小粟:技術部/可觀測技術組
小紅書可觀測技術工程師,畢業于西安交通大學,先后在推薦架構、云原生、可觀測領域從事相關工作,現專注于通用日志體系的建設。
蘇星河:技術部/可觀測技術組
小紅書可觀測技術工程師,畢業于南京大學計算機系,之前在小紅書供應鏈管理、大數據、推薦等諸多業務積累了豐富的經驗,最近在專注JVM在線診斷及性能優化相關的工作。