成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

探索可觀測(cè)的新視角—— eBPF 在小紅書的實(shí)踐

云計(jì)算
小紅書可觀測(cè)團(tuán)隊(duì)在過去一段時(shí)間內(nèi),對(duì) eBPF 等新技術(shù)在可觀測(cè)的應(yīng)用進(jìn)行了探索,在通用流量分析、持續(xù) Profiling 等領(lǐng)域進(jìn)行落地,解決了之前碰到的一些痛點(diǎn)問題。通過 eBPF 技術(shù)的應(yīng)用,團(tuán)隊(duì)將可觀測(cè)能力從應(yīng)用程序擴(kuò)展到了內(nèi)核,實(shí)現(xiàn)了對(duì)可觀測(cè)領(lǐng)域的進(jìn)一步擴(kuò)展。

在當(dāng)前的云原生時(shí)代,隨著微服務(wù)架構(gòu)的廣泛應(yīng)用,云原生可觀測(cè)性概念被廣泛討論。可觀測(cè)技術(shù)建設(shè),將有助于跟蹤、了解和診斷生產(chǎn)環(huán)境問題,輔助開發(fā)和運(yùn)維人員快速發(fā)現(xiàn)、定位和解決問題,支撐風(fēng)險(xiǎn)追溯、經(jīng)驗(yàn)沉淀、故障預(yù)警,提升系統(tǒng)可靠性。云原生和微服務(wù)技術(shù)的不斷深入應(yīng)用給可觀測(cè)提出了新的需求,在 Metrics、Logging、Tracing 等傳統(tǒng)可觀測(cè)范疇外,我們需要探索新的技術(shù)和方案。

小紅書可觀測(cè)團(tuán)隊(duì)在過去一段時(shí)間內(nèi),對(duì) eBPF 等新技術(shù)在可觀測(cè)的應(yīng)用進(jìn)行了探索,在通用流量分析、持續(xù) Profiling 等領(lǐng)域進(jìn)行落地,解決了之前碰到的一些痛點(diǎn)問題。通過 eBPF 技術(shù)的應(yīng)用,團(tuán)隊(duì)將可觀測(cè)能力從應(yīng)用程序擴(kuò)展到了內(nèi)核,實(shí)現(xiàn)了對(duì)可觀測(cè)領(lǐng)域的進(jìn)一步擴(kuò)展。

01背景

在過去一段時(shí)間里,我們?cè)谏a(chǎn)上遇到了一些實(shí)際問題,如中臺(tái)服務(wù)被多上游服務(wù)訪問、或者提供 OpenApi 供外部服務(wù)調(diào)用,有時(shí)候會(huì)碰到接收到的流量異常上漲,自身應(yīng)用在流量異常上漲的情況下CPU、內(nèi)存可能會(huì)跟著飆升,往往會(huì)影響應(yīng)用自身的穩(wěn)定性的情況。更麻煩的是,此時(shí)我們有時(shí)候并且不知道調(diào)用方在哪。甚至存在開發(fā)環(huán)境訪問線上環(huán)境、跨機(jī)房訪問等情況。如下圖所示,可觀測(cè)的主機(jī)監(jiān)控存儲(chǔ)集群,被未知的上游服務(wù)定期拉取數(shù)據(jù),導(dǎo)致 CPU、內(nèi)存異常上漲,嚴(yán)重的影響監(jiān)控存儲(chǔ)本身的穩(wěn)定性。

圖片

想要解決類似的問題,需要對(duì)流量進(jìn)行實(shí)時(shí)的分析,并且做到語言、架構(gòu)無關(guān)。而傳統(tǒng)的可觀測(cè)領(lǐng)域,缺少解決這種實(shí)時(shí)流量分析的通用手段。

此外,業(yè)務(wù)對(duì)于 C++ 性能退化的識(shí)別是一個(gè)普遍的訴求,之前我們對(duì) C++服務(wù)的持續(xù) Profiling 和性能退化檢測(cè)中,碰到了一些阻礙,其中主要的困難在于基于傳統(tǒng)的 linux perf 的方式,使用frame pointer 方式的回溯可能會(huì)出現(xiàn)結(jié)果不準(zhǔn)確;使用 dwarf 方式的回溯會(huì)出現(xiàn)性能開銷比較大、耗時(shí)很長(zhǎng)的問題。這些導(dǎo)致常態(tài)化 Profiling 無法實(shí)現(xiàn),缺少低開銷且通用的解決方案。

基于以上背景,我們注意到了近些年在各領(lǐng)域興起并得到應(yīng)用的 eBPF 技術(shù),可以在 Linux 確定的內(nèi)核函數(shù) Hook 點(diǎn)運(yùn)行,來執(zhí)行用戶設(shè)定好的邏輯,常見的如對(duì)網(wǎng)絡(luò)數(shù)據(jù)包的監(jiān)控、性能統(tǒng)計(jì)和安全審計(jì)等功能。我們初步判斷,eBPF 的這些特性能夠解決困擾我們的這些問題。

同時(shí),eBPF 作為近些年來的 Linux 社區(qū)的新寵,受到了國內(nèi)外互聯(lián)網(wǎng)大廠的青睞,在多個(gè)領(lǐng)域都得到了應(yīng)用。國內(nèi)各大互聯(lián)網(wǎng)公司的基礎(chǔ)架構(gòu)部門也都在落地 eBPF,如字節(jié)、阿里、騰訊百度等等,都有著eBPF的落地經(jīng)歷。基于 eBPF 的開源項(xiàng)目也像雨后春筍一樣涌現(xiàn)出來。

所以我們嘗試把 eBPF 在可觀測(cè)所面臨的問題場(chǎng)景中進(jìn)行落地,來解決我們遇到的一些痛點(diǎn)問題,最終服務(wù)好業(yè)務(wù)的穩(wěn)定性。

02eBPF簡(jiǎn)介

eBPF(extended Berkeley Packet Filter),是對(duì) BPF (Berkeley Packet Filter) 技術(shù)的擴(kuò)展。通過在內(nèi)核中運(yùn)行沙盒程序,eBPF 允許程序在不修改內(nèi)核源代碼或加載內(nèi)核模塊的前提下,擴(kuò)展內(nèi)核的能力。隨著 eBPF 技術(shù)的不斷完善和加強(qiáng),eBPF 已經(jīng)不再局限于定義中的網(wǎng)絡(luò)數(shù)據(jù)包的過濾,在可觀測(cè)、安全、網(wǎng)絡(luò)等方面得到了廣泛的應(yīng)用。

圖片

傳統(tǒng)意義上的觀測(cè)性,是指在外部洞悉應(yīng)用程序運(yùn)行狀況的能力。基于 eBPF 可以無需侵入到應(yīng)用程序內(nèi)部、直接向內(nèi)核添加代碼來收集數(shù)據(jù)的特點(diǎn),我們可以直接從內(nèi)核中收集、聚合自定義的數(shù)據(jù)指標(biāo)。通過這種方式,我們將可觀測(cè)性擴(kuò)展從應(yīng)用程序擴(kuò)展到了內(nèi)核,實(shí)現(xiàn)對(duì)可觀測(cè)領(lǐng)域的進(jìn)一步擴(kuò)展。

常見的內(nèi)核 Event 如下圖所示:

圖片

在這些 Hook 點(diǎn)上,都可以編寫應(yīng)用程序來實(shí)現(xiàn)可觀測(cè)能力的覆蓋,同時(shí)探索更多深度觀測(cè)能力。

針對(duì)實(shí)際工作中遇到的痛點(diǎn),我們基于 eBPF 技術(shù),在流量分析和 Profiling 中進(jìn)行了探索,下文分別對(duì)這兩個(gè)方面進(jìn)行詳細(xì)的介紹。

03在流量分析的

在流量分析場(chǎng)景下,目前我們主要聚焦在 L4、L7 層:L4 層得到流量包的大小,L7 層進(jìn)一步得到 QPS、RPC Method 等信息。

整體架構(gòu)如下所示:


圖片

我們的 eBPF Agent 以 DaemonSet 方式部署,在啟動(dòng)過程中,將 eBPF 程序加載到內(nèi)核中,Hook 內(nèi)核的 Tcp 數(shù)據(jù)收發(fā)等系統(tǒng)調(diào)用。主要流程:

  • Agent 通過接收下發(fā)的流量采集配置,在所在的 Node 上查找目標(biāo)進(jìn)程,在找到目標(biāo)進(jìn)程后,將目標(biāo)進(jìn)程號(hào)(Pid)傳遞到內(nèi)核中。
  • 內(nèi)核態(tài)的 eBPF 程序收到Pid信息后,開始采集流量并做輕量級(jí)的處理,并將數(shù)據(jù)發(fā)送到 eBPF Map 中。
  • 用戶態(tài)的 eBPF Agent 讀取 eBPF Map 中的數(shù)據(jù),做聚合和處理,生成 Metrics 指標(biāo)。
  • eBPF Collector 集中式的采集各個(gè) eBPF Agent 生成的 Metrics 指標(biāo);在采集到指標(biāo)后,根據(jù)指標(biāo)中的上下游 IP 信息來查詢 Meta 服務(wù),獲取到對(duì)應(yīng)的應(yīng)用信息,并補(bǔ)充到 Metrics 指標(biāo)中;最終寫入到 Vms 存儲(chǔ)供查詢。

下面分別從內(nèi)核態(tài)、用戶態(tài)、eBPF Collector 等幾個(gè)方面來詳細(xì)的闡述。

3.1. 內(nèi)核態(tài)

3.1.1. L4層流量

一個(gè)典型的 Client-Server 之間的收發(fā)包流程,如下:

圖片

Client-Server之間,先建立連接:Server 通過 bind、listen 來監(jiān)聽端口,Client 通過 connect 來與 Server 創(chuàng)建連接;Server 在監(jiān)聽到這個(gè)請(qǐng)求之后,會(huì)調(diào)用 accept 函數(shù)取接收請(qǐng)求,這樣就建立了連接。建立連接之后,Client 可以發(fā)出數(shù)據(jù)包,在L4層,關(guān)鍵函數(shù)是 tcp_sendmsg。

基于上面的流程,我們主要關(guān)注的 Hook 點(diǎn)如下:

圖片

其中,對(duì)于 Server 來說,我們沒有 Hook tcp_recvmsg,而是 Hook tcp_cleanup_rbuf。這主要是因?yàn)橐环矫?tcp_recvmsg 可能存在統(tǒng)計(jì)上的遺漏和重復(fù);另一方面,tcp_cleanup_rbuf 的執(zhí)行次數(shù)低于 tcp_recvmsg,可以降低消耗。

在Hook tcp_sendmsg和tcp_cleanup_rbuf中,根據(jù)struct sock對(duì)象,拿到上下游的IP、Port、數(shù)據(jù)包大小等關(guān)鍵信息,并Output到用戶態(tài)。

3.1.2. L7層流量

L4層面的流量提供了網(wǎng)絡(luò)流量大小。有時(shí)候,我們還想要知道更多的信息,如QPS、延遲、消息協(xié)議、Rpc Method、Redis 命令等等。在這種情況下,我們需要進(jìn)一步來實(shí)現(xiàn) L7 層的流量分析功能。

我們通過Hook讀寫相關(guān)的系統(tǒng)調(diào)用,來獲取到服務(wù)之間的流量數(shù)據(jù),常見的讀寫相關(guān)的系統(tǒng)調(diào)用,如下:

圖片

通過Hook這些系統(tǒng)調(diào)用,我們最終需要拿到的是原始的報(bào)文buf數(shù)據(jù)、對(duì)端地址信息(socket address),并基于 buf 數(shù)據(jù)和 socket address 處理得到 QPS、協(xié)議、RPC method 等信息。

基本流程如下:

  • 通過 tracepoint/probe 追蹤 socket syscall 相關(guān)的函數(shù),Hook 并根據(jù) Pid 進(jìn)行過濾,保留 Pid 收發(fā)的流量 buf 數(shù)據(jù);
  • 根據(jù) buf 數(shù)據(jù),提取 socket 元信息,獲取 socket address;
  • 根據(jù) buf 數(shù)據(jù),進(jìn)行相應(yīng)的協(xié)議推斷,判斷是否是我們支持的協(xié)議,不是則設(shè)置為 unknown;
  • 將原始的流量 buf 數(shù)據(jù) Output 到用戶態(tài),供進(jìn)一步處理。

其中,兩個(gè)關(guān)鍵的過程分別是獲取 socket address、協(xié)議推斷。

socket address獲取:

一個(gè)方案是Hook 建立連接的系統(tǒng)調(diào)用,如 sys_enter_connect、inet_sock_set_state 等,并解析參數(shù)中的 skaddr 的信息來拿到 IP、Port。這種方案的優(yōu)點(diǎn)是簡(jiǎn)單易實(shí)現(xiàn),但是這種方案的問題是,對(duì)于在我們 Agent 部署之前就存在的長(zhǎng)連接來說,我們無法捕獲到相應(yīng)的事件和相應(yīng)的信息。

我們采用的方法是是通過 bpf_get_current_task 來拿到 task_struct 類型的 task,來獲取 socket 對(duì)象,進(jìn)而拿到 sockaddr:

  • task_struct 中的 files 字段,類型為 files_struct;
  • 根據(jù) files 拿到 fdtable 字段,是當(dāng)前進(jìn)程的文件描述符表;
  • 再從 fdtable 中,根據(jù) socket 的 fd,拿到 socket 的 file 結(jié)構(gòu);
  • socket 的 file 中,有個(gè) sock 類型的 sk 對(duì)象,就是 socket 的內(nèi)核對(duì)象指針;根據(jù) sk 對(duì)象,就可以得到 IP、Port、UDP 還是 TCP、IPV4 還是 IPV6 等各種屬性。

協(xié)議推斷:

根據(jù)上述列出來的讀寫系統(tǒng)調(diào)用中,拿到的原始的字節(jié)流 buf 數(shù)據(jù),可以來嘗試解析對(duì)應(yīng)的應(yīng)用協(xié)議,直接遵照協(xié)議規(guī)范進(jìn)行解析。當(dāng)前常見的協(xié)議如 Http1、Thrift、Redis、Baidu-std 等,目前我們都已經(jīng)支持了;后續(xù)會(huì)支持如 Mysql 等更多協(xié)議的解析推斷。

此外,在協(xié)議的解析推斷過程中,另外一個(gè)問題是業(yè)務(wù)消息的拆分和重組:在實(shí)際中業(yè)務(wù)進(jìn)程的一次數(shù)據(jù)收發(fā),在系統(tǒng)調(diào)用層面,可能會(huì)拆分成多次系統(tǒng)調(diào)用來進(jìn)行讀寫,可能會(huì)導(dǎo)致后續(xù)的 buf 都無法正確的解析出協(xié)議來。

為了解決這種問題,當(dāng)一個(gè) socket 上的 buf 數(shù)據(jù)在協(xié)議推斷成功后,將 socket 和協(xié)議信息保存在 socket info 中,并將 socket info 進(jìn)行緩存;該 socket 上后續(xù)的 buf 數(shù)據(jù)在協(xié)議解析推斷失敗后,會(huì)默認(rèn)使用該 socket info 中的協(xié)議信息;如果后續(xù) buf 數(shù)據(jù)協(xié)議解析成功且多次不同時(shí),對(duì)協(xié)議進(jìn)行覆蓋。這樣,可以盡可能降低解析錯(cuò)誤的概率。最后,Hook close 系統(tǒng)調(diào)用,在 socket close 的時(shí)候,把 socket info 清理掉。此外,利用數(shù)據(jù)包之間的關(guān)聯(lián)來判斷協(xié)議,即 request、response 之間的協(xié)議應(yīng)該是一樣的,來進(jìn)一步降低解析錯(cuò)誤的概率。

3.1.3. 內(nèi)核適配

基于 eBPF,應(yīng)用開發(fā)的模式主要有兩種:

  • BPF 編譯器集合 (BCC Tools) 工具包提供了許多有用的資源和示例來構(gòu)建有效的內(nèi)核跟蹤和操作程序。
  • BPF CO-RE (Compile Once – Run Everywhere)是與 BCC 框架不同的開發(fā)部署模式,使用 BTF來解決編譯依賴問題。

BCC的優(yōu)點(diǎn)是提供了很多有用的示例,同時(shí)還有多種前端語言(主要是用戶態(tài)用來處理加載到內(nèi)核態(tài)BPF程序的輸出和交互)來輔助進(jìn)行編程,如Python、Golang。存在的問題是:

  • 使用 Clang 修改編寫的 BPF 程序,當(dāng)出現(xiàn)問題時(shí),排查問題更加困難。
  • 類似一種動(dòng)態(tài)語言的方式,BPF 程序是在運(yùn)行時(shí)編譯的,編譯的時(shí)候需要工具鏈和內(nèi)核文件。編譯依賴是脆弱的、容易失敗,所以總體不可控,兼容性不夠好。
  • 應(yīng)用在啟動(dòng)時(shí),編譯BPF程序會(huì)占用大量的CPU和內(nèi)存資源,在大量的低規(guī)格的機(jī)器上,可能會(huì)影響業(yè)務(wù)進(jìn)程。

這些問題,特別是兼容性問題和性能問題,對(duì)于我們想要在線上大規(guī)模部署的話,是很大的阻礙。

BPF CO-RE 依賴內(nèi)核特性支持 BTF,將內(nèi)核的數(shù)據(jù)結(jié)構(gòu)類型構(gòu)建在內(nèi)核中。用戶態(tài)的程序可以導(dǎo)出 BTF 成一個(gè)單獨(dú)的大的.h 頭文件(如vmlinux.h),這個(gè)頭文件包含了所有的內(nèi)核內(nèi)部類型,BPF 程序只要依賴這個(gè)頭文件就行,不需要安裝內(nèi)核頭文件的包了。這樣就可以減少依賴,進(jìn)行提前編譯。

因此,考慮到我們需要大規(guī)模部署并且長(zhǎng)時(shí)間運(yùn)行,我們需要盡可能降低資源占用、提高性能,我們選擇了 CO-RE 方式。

使用 BTF 機(jī)制,需要內(nèi)核開啟了 CONFIG_DEBUG_INFO_BTF選項(xiàng)(CONFIG_DEBUG_INFO_BTF=y)。在我們上線覆蓋過程中,遇到了部分機(jī)器的內(nèi)核是5.4,同時(shí)沒有開啟 CONFIG_DEBUG_INFO_BTF 選項(xiàng)。對(duì)于這些沒有開啟的內(nèi)核,我們生成并導(dǎo)入對(duì)應(yīng)版本的 BTF 文件;我們的 eBPF Agent在啟動(dòng)時(shí)先檢測(cè)內(nèi)核版本和 CONFIG_DEBUG_INFO_BTF 選項(xiàng);如果選項(xiàng)沒有開啟,則根據(jù)內(nèi)核版本加載對(duì)應(yīng)的 BTF 文件。當(dāng)前我們對(duì)線上主要的5.4、5.10的多個(gè)內(nèi)核版本做了適配。

3.2. 用戶態(tài)

用戶態(tài)的主要工作是通過接收采集配置來選擇出目標(biāo) Pid,傳遞給內(nèi)核 eBPF 程序來開啟流量分析;從內(nèi)核中讀取流量數(shù)據(jù)并處理。基本的示意圖如下:

圖片

3.2.1. 生效機(jī)制

在實(shí)際應(yīng)用中,如果采集 Node 上所有的流量數(shù)據(jù),消耗會(huì)很大;同時(shí)大量的未知流量信息會(huì)帶來很大的干擾。因此,我們需要在 Node 上選擇出我們實(shí)際關(guān)注的 Pid,同時(shí)將 Pid 信息傳遞到內(nèi)核中,在內(nèi)核流量采集分析的時(shí)候,根據(jù)Pid進(jìn)行過濾。

當(dāng)前流量分析功能是按需開啟的。在 Node 上部署 eBPF Agent 后,通過配置中心下發(fā)配置來決定對(duì)哪些服務(wù)開啟、開啟的 K8S 集群,以及生效的比例等。Agent 在接收到配置后,根據(jù) Pod 過濾規(guī)則,在所屬的 Node 上查找匹配到的 Pod;在 Pod 的各個(gè) Container 中,根據(jù) Container name 查找匹配的 Container;最后根據(jù) K8S 集群信息和 Pid name,在 Container 中匹配到 Pid。

匹配到 Pid 之后,將 Pid 傳遞給內(nèi)核 eBPF 程序來開啟采集。在需要關(guān)閉采集的時(shí)候,將 Pid 從內(nèi)核中刪除即可。

3.2.2 eBPF C 程序管理

在拿到 Pids 之后,我們將 eBPF C程序、相關(guān)的 eBPF Map 以及 Pids 加載到內(nèi)核中,這里涉及到 eBPF C程序的管理和數(shù)據(jù)交互。

為了簡(jiǎn)化 eBPF C代碼的開發(fā)和調(diào)試流程,我們支持了配置化的對(duì) eBPF 程序的加載、卸載、數(shù)據(jù)讀取等。整體結(jié)構(gòu)如下:

圖片

編譯&加載:

eBPF 的 C 代碼,使用 Clang 和 LLVM 工具鏈來編譯 eBPF 代碼,生成可加載的字節(jié)碼文件。將字節(jié)碼文件作為 ELF 文件資源進(jìn)行讀取,并解析其中的 Maps、Program 等。在解析之后,通過 BPF 系統(tǒng)調(diào)用:對(duì) Maps 進(jìn)行 BPF_MAP_CREATE 創(chuàng)建 Maps;對(duì) Program 進(jìn)行BPF_PROG_LOAD。這樣將字節(jié)碼加載到內(nèi)核中并進(jìn)行安全驗(yàn)證。

Link:

根據(jù)配置文件中配置的 probe、tracepoint 信息,通過 BPF_LINK_CREATE BPF 系統(tǒng)調(diào)用,將 eBPF 程序掛載到對(duì)應(yīng)的內(nèi)核事件上,從而實(shí)現(xiàn)對(duì)這些事件的監(jiān)聽,當(dāng)內(nèi)核執(zhí)行到對(duì)應(yīng)的事件,會(huì)觸發(fā)并執(zhí)行對(duì)應(yīng)的 eBPF 程序邏輯。

數(shù)據(jù)讀取:

Map 是 eBPF 內(nèi)核程序和用戶態(tài)程序之間交互的橋梁。在用戶態(tài)中,根據(jù)配置文件中配置的 Map,啟動(dòng) Epoll 來讀取 Map 中的數(shù)據(jù)。

3.2.3. 內(nèi)核數(shù)據(jù)接收與處理

圖片

eBPF C程序被加載進(jìn)內(nèi)核后,代理程序(eBPF Agent)便開始通過 Epoll 機(jī)制讀取 eBPF Map 中的數(shù)據(jù)。這些數(shù)據(jù)包含了業(yè)務(wù)模塊間直接交換的原始流量。

采樣流量開關(guān):對(duì)于一些輕量級(jí)的 Proxy 服務(wù),往往單個(gè)實(shí)例的流量很大;同時(shí),單個(gè) Node 上可能部署多個(gè)實(shí)例,這樣一來 Node 上部署的eBPF Agent 采集流量并做處理的壓力就很大。為了解決 eBPF Agent 流量處理壓力大的問題,eBPF Agent 實(shí)現(xiàn)了流量采樣機(jī)制,Agent 通過配置中心獲取采樣比例配置,通過 eBPF Map 將配置信息傳給內(nèi)核。eBPF Agent 也通過配置中心配置下發(fā)實(shí)現(xiàn)了更細(xì)粒度的流量開關(guān),能精確控制 L4/L7 的進(jìn)/出不同方向的流量采集,按需開啟,來實(shí)現(xiàn)節(jié)約資源消耗的目的。

流量數(shù)據(jù)解析:當(dāng)流量數(shù)據(jù)傳到用戶側(cè)時(shí) Agent 根據(jù) L7 協(xié)議規(guī)范進(jìn)一步解析并提供更多信息:在如網(wǎng)關(guān)場(chǎng)景下,通過精準(zhǔn)解析 HTTP 消息,可以實(shí)時(shí)獲取到請(qǐng)求的實(shí)際 IP;在 RPC 場(chǎng)景下,通過遞歸解析Thrift消息,可以識(shí)別 RPC 方法,任意 RPC 參數(shù)等信息(比如排序服務(wù)的模型信息);在 Redis 場(chǎng)景下,可以解析Redis命令。

指標(biāo)數(shù)據(jù)生成:在解析補(bǔ)全 L7 流量信息后,Agent 將消息事件進(jìn)行哈希后放入 Queue 中,保證后續(xù)構(gòu)成相同指標(biāo)的事件總是被緩存在同一個(gè)隊(duì)列中。在消費(fèi) Queue 中緩存的消息事件時(shí),消息事件流量 IP、方向、協(xié)議等信息被聚合為流量指標(biāo);同時(shí)將流量指標(biāo)根據(jù)采樣率進(jìn)行流量還原,最終生成 Prometheus 格式的 Metrics 指標(biāo)。此外,為了控制資源消耗、內(nèi)存使用和監(jiān)控指標(biāo)的過度膨脹,Agent 會(huì)在實(shí)例IP變動(dòng)后,需要及時(shí)進(jìn)行數(shù)據(jù)過期清理。

3.3. 指標(biāo)采集和處理

對(duì)于 L4、L7 層流量數(shù)據(jù)來說,我們?cè)谟脩魬B(tài)拿到的數(shù)據(jù)中,包含了上下游服務(wù)的 IP、Port。實(shí)際生產(chǎn)環(huán)境中,上下游服務(wù)實(shí)例非常多,并且隨著應(yīng)用發(fā)布會(huì)不斷變化,單純提供IP對(duì)開發(fā)和運(yùn)維同學(xué)的幫助不大。因此,我們需要將 IP、Port 關(guān)聯(lián)出所屬的應(yīng)用、服務(wù),并提供更多的相關(guān)信息,如 Region、K8S 集群等信息。

我們部署 eBPF-Collector 來統(tǒng)一采集部署的eBPF Agent的指標(biāo)數(shù)據(jù),處理后進(jìn)行存儲(chǔ)。

3.3.1 元數(shù)據(jù)關(guān)聯(lián)

我們通過 CMDB 查詢出 IP:Port 對(duì)應(yīng)的應(yīng)用名/區(qū)域等服務(wù)元信息。由于指標(biāo)數(shù)據(jù)量巨大,不可能為每一個(gè)數(shù)據(jù)點(diǎn)請(qǐng)求一次 CMDB 來獲取元信息,因此我們?cè)O(shè)計(jì)了元信息緩存來加速查詢。

Cache 整體架構(gòu)

我們最初將元信息緩存設(shè)計(jì)為指標(biāo)采集服務(wù)(eBPF Collector)的本地內(nèi)存緩存。但是由于相同的 IP:Port 查詢請(qǐng)求會(huì)等概率地出現(xiàn)在所有采集分片中,采集分片的本地緩存會(huì)保存幾乎全部被用到的數(shù)據(jù)。在水平擴(kuò)容分片時(shí),本地內(nèi)存緩存數(shù)目也會(huì)成倍增加,這意味著當(dāng)緩存更新時(shí),緩存對(duì) CMDB 的請(qǐng)求數(shù)目也會(huì)隨服務(wù)分片數(shù)成倍增加,這會(huì)對(duì) CMDB 服務(wù)造成巨大查詢壓力。為了解決這一問題,我們重新設(shè)計(jì)了如下圖所示的新的 Cache Server 結(jié)構(gòu):

圖片

我們將緩存服務(wù)獨(dú)立部署為單獨(dú)的 Cache Server,與指標(biāo)采集服務(wù)隔離。這解除了指標(biāo)采集服務(wù)和元信息緩存的耦合,防止指標(biāo)采集服務(wù)水平擴(kuò)容帶來的元信息重復(fù)請(qǐng)求問題。

Cache 內(nèi)部結(jié)構(gòu)

圖片

元信息緩存是基于 Working Set 的思路設(shè)計(jì)的,我們將查詢到的元信息存儲(chǔ)一段時(shí)間,同時(shí)使用 Singleflight 機(jī)制,合并同一時(shí)刻出現(xiàn)的相同的元信息查詢請(qǐng)求,降低對(duì) CMDB 的請(qǐng)求并發(fā)度。

為了降低查詢延遲,緩存除了根據(jù)預(yù)先確定的 TTL 刪除一段時(shí)間內(nèi)未訪問的元信息,還會(huì)對(duì)仍在緩存中的元信息每隔若干時(shí)間進(jìn)行后臺(tái)刷新來更新數(shù)據(jù)。

由于緩存的元信息都保存在內(nèi)存中,Cache Server 服務(wù)重啟/發(fā)布后會(huì)導(dǎo)致緩存的數(shù)據(jù)丟失。這意味著每次啟動(dòng)都需要幾乎大量拉取 CMDB 元信息,我們?yōu)榫彺娣?wù)添加了緩存持久化功能,緩存服務(wù)會(huì)將緩存持久化在硬盤中,重啟后直接嘗試讀取舊緩存,防止冷啟動(dòng)問題。

3.3.2. 查詢性能優(yōu)化

eBPF 的網(wǎng)絡(luò)流指標(biāo)量非常大,一次采樣周期內(nèi)采集到的指標(biāo)量超過1.1億;并且高度集中在L4、L7的三四個(gè)指標(biāo)中,這給指標(biāo)查詢帶來了巨大壓力,日常可查詢的時(shí)間范圍不超過一天,并且經(jīng)常查詢超時(shí)。

但是相對(duì)而言,eBPF 指標(biāo)的查詢方式比較固定,所以我們可以根據(jù)預(yù)先定義的 PromQL 查詢對(duì)指標(biāo)進(jìn)行流式預(yù)聚合,將預(yù)聚合之后的指標(biāo)寫入存儲(chǔ)。這相當(dāng)于將指標(biāo)鏈路中采集之后鏈路(比如存儲(chǔ)/查詢)的計(jì)算壓力前置,大幅降低寫入存儲(chǔ)的指標(biāo)量,進(jìn)而減少查詢的數(shù)據(jù)量,加快查詢速度和可查詢的時(shí)間范圍。整體過程如下圖:

圖片

就具體實(shí)現(xiàn)來說,我們通過配置中心下發(fā)預(yù)聚合配置,當(dāng)配置有變更時(shí),服務(wù)會(huì)原子地更新預(yù)聚合算子(Operator)并重置預(yù)聚合狀態(tài)。

當(dāng)指標(biāo)數(shù)據(jù)到達(dá)預(yù)聚合服務(wù)時(shí),數(shù)據(jù)會(huì)被復(fù)制一份,復(fù)制后的數(shù)據(jù)會(huì)經(jīng)過預(yù)聚合 State Operator 來計(jì)算得到預(yù)聚合中間狀態(tài),并保存在內(nèi)存中;根據(jù)配置不同,每隔若干時(shí)間(比如 30s)服務(wù)會(huì)將中間狀態(tài)通過 Merge Operator 合并為聚合后的數(shù)據(jù),并寫入游數(shù)據(jù)源。

為了保證數(shù)據(jù)的完整性,預(yù)聚合服務(wù)起停時(shí)的最近聚合數(shù)據(jù)會(huì)被丟棄。對(duì)于單副本預(yù)聚合服務(wù),服務(wù)起停時(shí)指標(biāo)可能出現(xiàn)斷點(diǎn),我們使用雙副本加上數(shù)據(jù)去重來避免這個(gè)問題。

經(jīng)過對(duì)比驗(yàn)證,我們測(cè)試發(fā)現(xiàn)通過指標(biāo)預(yù)聚合,指標(biāo)查詢速度提升能 10 倍以上;查詢時(shí)間范圍從一天延長(zhǎng)至至少一周以上。

3.4. 產(chǎn)品化&實(shí)際落地的場(chǎng)景

當(dāng)前的流量分析功能和“目標(biāo)應(yīng)用”的語言、框架無關(guān),接入時(shí)不需要業(yè)務(wù)方做任何修改,對(duì)業(yè)務(wù)無感知、無侵入。我們?cè)诓渴?Agent 并發(fā)布配置后,就會(huì)產(chǎn)生實(shí)時(shí)的、持續(xù)的流量數(shù)據(jù),數(shù)據(jù)保存一個(gè)月。生效、取消生效的過程快速,秒級(jí)生效。

在性能上,在當(dāng)前所有覆蓋的場(chǎng)景下,eBPF Agent 日常平均CPU使用量在0.1 Core、內(nèi)存在200MB;CPU Limit設(shè)置為0.5 Core,內(nèi)存1GB,對(duì)業(yè)務(wù)基本無影響。

當(dāng)前在小紅書的Redis、KV存儲(chǔ)、推薦、廣告等場(chǎng)景規(guī)模落地,接入服務(wù)過一千。下面介紹流量分析的使用方式和一些實(shí)際 Case。

3.4.1. 產(chǎn)品

3.4.1.1. 流量大盤

L4層協(xié)議,當(dāng)前支持展示"目標(biāo)應(yīng)用"的流量大小。

作為服務(wù)端(Server),接收到上游請(qǐng)求的流量(MB/s)、返回給上游的流量(MB/s);作為客戶端(Client),請(qǐng)求下游的流量(MB/s)、接收到下游返回的流量(MB/s)。

圖片

上圖中展示的一個(gè)排序服務(wù)的L4層詳情:作為服務(wù)端(Server),接收到上游的請(qǐng)求流量(MB/s)、返回給上游的流量(MB/s);作為客戶端(Client)請(qǐng)求下游的流量(MB/s)、接收到下游返回的流量(MB/s)。

L7層的流量分析,例子如下:

圖片

當(dāng)前支持展示"目標(biāo)應(yīng)用":作為服務(wù)端(Server),接收到上游的請(qǐng)求QPS、返回給上游的QPS;作為客戶端(Client)請(qǐng)求下游的QPS、接收到下游返回的QPS。此外,還展示對(duì)應(yīng)的應(yīng)用協(xié)議(當(dāng)前支持Thrift、Redis、Http)、服務(wù)部署的Region(上海、南京、杭州)等信息。

此外,我們還提供了OpenApi接口,來查詢服務(wù)的上下游流量指標(biāo)情況。Redis、KV存儲(chǔ)等存儲(chǔ)服務(wù)的高可用架構(gòu)規(guī)范治理過程中,通過這種方式來獲取上游服務(wù)的來源和訪問情況。

3.4.1.2. 服務(wù)拓?fù)?/span>

基于 eBPF 的服務(wù)流量指標(biāo),我們可以構(gòu)造出服務(wù)之間拓?fù)潢P(guān)系,所有 A 服務(wù)發(fā)往 B 服務(wù)的流量都會(huì)聚合為服務(wù) A 到服務(wù) B 的一條邊,由此構(gòu)成拓?fù)鋱D。我們定期拉取一天的的流量指標(biāo),聚合出服務(wù)拓?fù)溥叄⑦呅畔⒋鎯?chǔ)在 Clickhouse 中。當(dāng)用戶查詢拓?fù)潢P(guān)系時(shí),服務(wù)從 Clickhouse 中取出拓?fù)溥呅畔?gòu)造拓?fù)鋱D。下圖展示了由 eBPF 流量指標(biāo)獲取的兩層拓?fù)鋱D:

圖片

3.4.2. 落地場(chǎng)景

在實(shí)際覆蓋過程中,流量分析可以輔助定位流量上漲的來源確定、偶發(fā)的流量、服務(wù)下線過程中的流量排空等問題。

Case 1.  服務(wù)下線前,偶發(fā)流量的來源定位

問題背景:電商的研發(fā)同學(xué)向我們咨詢,他們有個(gè)服務(wù)在準(zhǔn)備下線的時(shí)候,遇到個(gè)問題:還有偶發(fā)的、非常零星的上游流量會(huì)訪問他們的服務(wù),訪問的頻率在每小時(shí)十幾個(gè)請(qǐng)求。擔(dān)心貿(mào)然的下線會(huì)影響穩(wěn)定性,他們希望幫忙定位這些零星流量的來源。流量情況如下所示:

圖片

這種零星的流量,夾雜在日常的其他消息中,常規(guī)的抓包是很難定位的。我們的eBPF 流量來源分析,因?yàn)榭梢宰龅饺我鈺r(shí)刻、實(shí)時(shí)的流量采集,可以來解決這種問題。

我們?cè)诓渴鸩㈤_啟了 eBPF 的 100% 全采樣的流量分析。進(jìn)一步了解到業(yè)務(wù)同學(xué)關(guān)心的零星請(qǐng)求是特定的 Thrift Method,所以想要定位的話,需要在采集 Thrift 流量后,進(jìn)一步對(duì) Thrift 消息進(jìn)行解析和分析。經(jīng)過解析實(shí)際的消息并進(jìn)行 Thrift Method 聚合后,終于可以看到了小時(shí)級(jí)的偶發(fā)的流量來源,可以看到對(duì)應(yīng)的上游服務(wù) IP,如下所示:

圖片

根據(jù) IP 很快就成功的定位到了上游服務(wù),是一個(gè)很古老的前端 Node 服務(wù)。

Case2.  流量上漲的來源確定

問題背景:Redis 的一個(gè)集群,某晚上海區(qū)異常,流量大幅上漲導(dǎo)致集群被打掛,影響內(nèi)流初排成功率。初步找到的流量來源看起來不是真正的大頭,需要排查上游流量上漲的來源。

我們通過部署 eBPF Agent 并采集分析流量,在幾分鐘內(nèi),識(shí)別出真實(shí)的上游流量來源和流量大小,輔助業(yè)務(wù)同學(xué)進(jìn)行止損。

圖片


04在持續(xù)Profiling的應(yīng)用

對(duì)于 C++服務(wù)的 Profiling 和性能退化檢測(cè)來說,我們之前碰到了一些阻礙,其中主要的困難在于基于 linux perf 實(shí)現(xiàn)的常態(tài)化 Profiling 性能開銷比較大、耗時(shí)很長(zhǎng)。基于 linux perf 來 Profiling 的兩個(gè)主要步驟:

  • perf record 按固定頻率采集進(jìn)程內(nèi)各個(gè)線程棧信息, 生成性能事件
  • perf script 解析性能事件,轉(zhuǎn)換為可讀數(shù)據(jù),將棧幀地址轉(zhuǎn)換為對(duì)應(yīng)的函數(shù)名稱與所屬文件和行號(hào)

在第一步 perf record 采集性能事件中,一般使用 -g 參數(shù)來獲取完整的調(diào)用棧,默認(rèn)使用 frame pointer。然而公司內(nèi) C++ 服務(wù)往往會(huì)開啟編譯優(yōu)化選項(xiàng),frame pointer 不可用,導(dǎo)致 profiling 的結(jié)果很大程度上失真。為了保證覆蓋率,一般使用 -g dwarf 參數(shù),指定使用 dwarf 方式來回溯獲取調(diào)用棧。使用 dwarf 會(huì)遇到一個(gè)問題就是中間數(shù)據(jù)量大:為了后續(xù)回溯的需要,會(huì)將每個(gè) CPU 的完整棧從內(nèi)核拷貝出來;核數(shù)越多、采集時(shí)間越長(zhǎng),得到的棧數(shù)據(jù)就越大,以廣告的一個(gè)服務(wù)為例,采樣 10s 會(huì)生成將近 175MB 的數(shù)據(jù)。

第二步 perf script 解析性能事件,首先需要將第一步中的所有性能事件的棧進(jìn)行回溯,拿到完整的調(diào)用鏈棧幀地址;再將地址通過 addr2line 工具轉(zhuǎn)換為函數(shù)名稱和文件信息。這時(shí)遇到了第二個(gè)問題,由于數(shù)據(jù)量大,整個(gè)轉(zhuǎn)換過程耗費(fèi)大量 CPU,耗時(shí)也很久。

以廣告的一個(gè)服務(wù)為例,在服務(wù)的 Node 上部署 perf Agent,在 Agen t的 CPU Limits 為0.5 Core 的情況下,對(duì)服務(wù)進(jìn)行 Profiling;采樣 10s 的數(shù)據(jù)并處理,整體耗時(shí)將近 1 小時(shí),并且全程 CPU 打滿,如下圖所示:

圖片

這種資源消耗和耗時(shí)情況,對(duì)于需要大面積部署、常態(tài)化的持續(xù) Profiling并基于 Profiling 數(shù)據(jù)進(jìn)行分析性能退化來說,是基本不可行的。

針對(duì)這個(gè)問題,我們基于 eBPF 來重新實(shí)現(xiàn) C++ 的 Profiling,大幅降低 C++ 服務(wù)的 Profiling 資源消耗、整體耗時(shí),來實(shí)現(xiàn)真正的持續(xù) Profiling。核心的思路是:在 Node 上部署 Profiling Agent,在內(nèi)核性能事件生成后,直接在內(nèi)核繼續(xù)完成棧回溯和聚合,大幅降低拷貝到用戶態(tài)的棧數(shù)據(jù)量;通過 Collector 服務(wù)來集中式的采集各個(gè) Profiling Agent 產(chǎn)生的棧數(shù)據(jù)并做處理。這種模式下,Agent 的計(jì)算壓力很小,可以實(shí)現(xiàn)持續(xù) Profiling;Collector 的處理邏輯對(duì)各個(gè) Agent 可以復(fù)用,整體消耗低。總體架構(gòu)如下:

圖片

簡(jiǎn)要的過程如下:

  • eBPF Agent 用戶態(tài)程序根據(jù)下發(fā)的采集配置,獲取目前服務(wù)的 Pid。根據(jù) Pid,獲取對(duì)應(yīng)的內(nèi)存分布信息以及使用的可執(zhí)行文件內(nèi)容,預(yù)處理后通過 eBPF Map 傳遞給內(nèi)核態(tài)供棧回溯時(shí)查找;
  • eBPF Agent 內(nèi)核態(tài)程序由 CPU Cycles 性能采樣事件觸發(fā),從當(dāng)前執(zhí)行位置回溯得到完整調(diào)用棧,聚合并保存在 eBPF Map 中;
  • eBPF Agent 用戶態(tài)按固定頻率從 eBPF Map 獲取每條調(diào)用棧的命中次數(shù),轉(zhuǎn)化為 pprof 格式數(shù)據(jù);
  • eBPF Collector 按固定頻率從 eBPF Agent 獲取 pprof 格式數(shù)據(jù);完成符號(hào)解析、生成火焰圖,并寫入存儲(chǔ);
  • 寫入的數(shù)據(jù)支持實(shí)時(shí)火焰圖、性能對(duì)比分析、性能退化監(jiān)測(cè)等功能。

下面分別從內(nèi)核態(tài)、eBPF agent用戶態(tài)、eBPF Collector 分別詳細(xì)的介紹。

4.1 內(nèi)核態(tài)

相比于 linux perf 將棧拷貝到用戶態(tài)后再做回溯,我們選擇借助 eBPF 提供的能力在內(nèi)核態(tài)直接完成回溯。這樣帶來了很多好處:

  • 大幅減少內(nèi)核態(tài)到用戶態(tài)的數(shù)據(jù)拷貝。
  • 在內(nèi)核態(tài)完成回溯后,重復(fù)命中采樣的調(diào)用棧可以直接分組累積,數(shù)據(jù)量不隨采集時(shí)間線型增長(zhǎng)。

下面具體介紹我們?cè)趦?nèi)核態(tài)做了哪些工作,以及如何在內(nèi)核態(tài)完成基于 dwarf 的棧回溯的。

圖片

eBPF 原生支持 linux perf 的性能事件, 我們只需要編寫對(duì)應(yīng)的 eBPF 程序加載到對(duì)應(yīng)的 perf event 就可以按固定的采樣頻率觸發(fā) eBPF 程序回調(diào)。

eBPF 程序會(huì)讀取當(dāng)前的三個(gè)寄存器(ip: 指向下一條指令, sp: 棧頂?shù)刂? bp: 棧幀基址),這三個(gè)寄存器的值是棧回溯過程的起點(diǎn)。回溯的過程就是將這三個(gè)寄存器的值反復(fù)地恢復(fù)到當(dāng)前函數(shù)被調(diào)用前的值,直到?jīng)]有函數(shù)調(diào)用為止。

回溯完成后更新獲得的調(diào)用棧的命中次數(shù)到 eBPF Map 中,待用戶側(cè)采集使用。

eBPF 的verfier機(jī)制會(huì)限制程序復(fù)雜度和指令條數(shù),但調(diào)用棧深度可能會(huì)很長(zhǎng),無法一次完成回溯。我們限制了 eBPF 程序中的循環(huán)次數(shù),當(dāng)循環(huán)完成仍未完成回溯時(shí),我們用尾調(diào)用的方式重新調(diào)用自身繼續(xù)回溯直到完成。

一般來說,內(nèi)核函數(shù)與 jit 生成的函數(shù)會(huì)使用 framepointer 方式調(diào)用壓棧,回溯也使用 framepointer。而大部分用戶態(tài)的函數(shù)經(jīng)過編譯優(yōu)化后 framepointer 不可用,需要使用 eh_frame 段中的 cfi 指令信息來輔助回溯。

下面具體解析函數(shù)的兩種回溯方式。

4.1.1. framepointer 方式回溯

framepointer 的意思就是使用一個(gè)獨(dú)立的寄存器保存棧基址,一般用來訪問函數(shù)參數(shù),也用來回溯函數(shù)調(diào)用, framepointer 一般就是指代 bp 寄存器。下圖是包含了 framepointer 的函數(shù)調(diào)用壓棧方式。

首先壓棧返回地址,也就是調(diào)用函數(shù)返回后繼續(xù)執(zhí)行的下一條指令

然后壓棧 bp 寄存器內(nèi)容,把 bp 寄存器更新為新的棧幀基址

圖片

回溯其實(shí)就是調(diào)用函數(shù)的反向過程,由于 bp 寄存器的內(nèi)容是棧基址,而棧基址所指的地方保存了 caller 函數(shù)的 bp。只要反復(fù)將 bp 寄存器的值作為指針讀取值更新到 bp,就可以完成回溯。

我們關(guān)心的函數(shù)的 ip 指令地址,可以基于 bp 偏移得到。

如果只是使用 framepointer 方式回溯,我們只需要 bp 和 ip 的內(nèi)容。但是實(shí)際程序運(yùn)行場(chǎng)景中往往會(huì)出現(xiàn)帶 framepointer 和不帶 framepointer 函數(shù)互相調(diào)用的情況,而不帶 framepointer 的函數(shù)需要使用 eh_frame 信息回溯,依賴 sp 寄存器。

所以我們也保留 sp 寄存器的值,也基于 bp 偏移得到。

4.1.2. eh_frame方式回溯

framepointer 方式占用了一個(gè)專用的寄存器,函數(shù)執(zhí)行過程中很少使用,而且每次需要壓棧。整體來說帶來了額外的內(nèi)存開銷。現(xiàn)代編譯器在開啟編譯優(yōu)化的情況下不再使用 framepointer,這個(gè)時(shí)候我們的 bp 寄存器不再保存棧幀基址,而是作為通用寄存器使用,提高了內(nèi)存效率。

不使用 framepointer 的函數(shù)在調(diào)用時(shí),不再壓棧 bp 寄存器了

圖片

但是函數(shù)調(diào)試/異常處理都需要用到回溯信息,沒有 framepointer 的函數(shù),它的回溯信息會(huì)在編譯期間通過插入 cfi 指令的方式記錄,cfi 指令最終會(huì)生成可執(zhí)行 elf 文件中的 .eh_frame 段。

cfi 指令示例

每當(dāng)發(fā)生棧變量分配和回收時(shí),編譯器生成一條 cfi 指令更新如何從棧頂找到棧基址的信息

每當(dāng)寄存器壓棧時(shí),編譯器生成一條 cfi 指令更新如何從棧基址恢復(fù)寄存器內(nèi)容的信息

圖片

回溯的思路類似 framepointer 方式,先拿到棧基址,通過棧基址偏移獲取其他關(guān)心的寄存器內(nèi)容。

cfi 指令一般記錄棧基址到棧頂?shù)木嚯x,每次回溯時(shí),我們讀取 sp 寄存器的內(nèi)容與對(duì)應(yīng)的 cfi 指令信息找到棧基址。有了棧基址再通過偏移找到 下一輪回溯使用的 bp ip sp 寄存器。

4.1.2.1. 使用回溯表簡(jiǎn)化 cfi 指令使用

由于需要在內(nèi)核側(cè) eBPF 程序中完成回溯,直接解析 cfi 指令過于復(fù)雜,我們將 cfi 指令生成 key 為指令地址的一張表,告訴回溯程序當(dāng)執(zhí)行到任意指令時(shí)如何找到棧基址,如何恢復(fù)寄存器內(nèi)容,表內(nèi)容如下圖:

圖片

4.1.2.2. 回溯表結(jié)構(gòu)設(shè)計(jì)

可執(zhí)行文件大小不一,指令數(shù)差異大,生成的回溯表大小不一,但 eBPF Map 的Key、Value 都是固定大小,為了高效存儲(chǔ)回溯表,我們使用兩個(gè) Map 分別作為數(shù)據(jù)表、索引表,如下圖所示:

圖片

數(shù)據(jù)表:用來保存具體回溯信息,由若干個(gè)shard組成,每個(gè)shard有數(shù)據(jù)量上限。對(duì)某個(gè)Pid開啟Profiling時(shí),對(duì)Pid的所有可執(zhí)行文件進(jìn)行遍歷和解析,生成回溯表后,將回溯表數(shù)據(jù)append 寫入數(shù)據(jù)表。寫入數(shù)據(jù)表過程是按 shard 依次寫滿。

索引表:提供可執(zhí)行文件到數(shù)據(jù)表之間的索引,定位可執(zhí)行文件關(guān)聯(lián)了數(shù)據(jù)表中分段。每次數(shù)據(jù)表寫滿一個(gè)shard 或當(dāng)前可執(zhí)行文件的回溯表寫完,在索引表中記錄一條數(shù)據(jù)表分段信息。

回溯表查找時(shí),首先根據(jù) pc (指令地址),在索引表找到可執(zhí)行文件對(duì)應(yīng)的所有分段,根據(jù)包含關(guān)系確定具體分段;最后根據(jù)分段對(duì)應(yīng)的數(shù)據(jù)表信息,在數(shù)據(jù)表中二分查找。

4.2 用戶態(tài)

圖片

4.2.1. 生成回溯表

讀取 Profiling 進(jìn)程的 Mapping (內(nèi)存地址分布,/proc/$pid/maps 文件),將用到的可執(zhí)行文件生成回溯表,寫入 eBPF Map 傳遞到內(nèi)核態(tài)。

進(jìn)程的 Mapping 不是固定的,部分情況下會(huì)發(fā)生改變,比如動(dòng)態(tài)鏈接庫加載,jit 代碼生成,這些都會(huì)在運(yùn)行時(shí)改變進(jìn)程 Mapping。

為了保證采樣數(shù)據(jù)的完整性和正確性,每次采集 Profiling 數(shù)據(jù)時(shí)我們先檢查 Mapping 可執(zhí)行的部分有沒有發(fā)生變化,如果變化就廢棄這一次采集,更新 eBPF Map 的內(nèi)容到 Mapping 的最新狀態(tài)。

4.2.2. 獲取性能采樣數(shù)據(jù)

定時(shí)采集內(nèi)核態(tài)暴露出來的調(diào)用棧和采樣次數(shù),按 Pid 聚合,為每個(gè) Pid 生成 pprof 格式的性能采樣數(shù)據(jù), 通過 http 接口暴露給采集側(cè)。

4.2.2.1. 內(nèi)核函數(shù)符號(hào)解析

我們?cè)?Agent 側(cè)完成內(nèi)核函數(shù)的符號(hào)解析(/proc/kallsyms 文件),因?yàn)椴煌?jié)點(diǎn)的內(nèi)核符號(hào)不同,內(nèi)核函數(shù)必須在本地解析。另外內(nèi)核函數(shù)的查找較為簡(jiǎn)單輕量。

用戶態(tài)函數(shù)我們不在 Agent 側(cè)解析,因?yàn)閮?nèi)聯(lián)函數(shù)的符號(hào)解析依賴 dwarf, 是個(gè)比較重的查找過程,要解析 debug_info 段,占用大量?jī)?nèi)存和CPU, 對(duì)于 Agent 來說負(fù)載過重。而且對(duì)不同節(jié)點(diǎn)部署的相同服務(wù)來說,符號(hào)解析是個(gè)重復(fù)動(dòng)作,放在采集側(cè)完成能更有效利用緩存,避免重復(fù)計(jì)算。

4.2.2.2. 關(guān)聯(lián)元數(shù)據(jù)標(biāo)簽

在發(fā)現(xiàn) Profiling 進(jìn)程的過程中,我們已經(jīng)保留了進(jìn)程的元數(shù)據(jù),包括 Pod 名稱、鏡像版本、可用區(qū)等等,這些信息作為標(biāo)簽附加在性能采樣數(shù)據(jù)中,方便后續(xù)實(shí)現(xiàn)過濾下鉆查詢。

為何使用 pprof 格式:有豐富工具類庫可使用;序列化壓縮效率高,所有字符串通過 id 引用;opentelemetry 規(guī)范中 profiling 數(shù)據(jù)模型是基于 pprof 格式設(shè)計(jì)的, 使用 pprof 方便后續(xù)對(duì)接業(yè)界規(guī)范。

4.2.3 精簡(jiǎn)可執(zhí)行文件

精簡(jiǎn)可執(zhí)行文件,抽取包含符號(hào)信息的分段,傳遞給采集側(cè)供符號(hào)解析時(shí)使用。

符號(hào)信息有2種來源,一種是可執(zhí)行文件的 .symtab 段,另一種是 dwarf。我們抽取這些分段合成一個(gè)精簡(jiǎn)過的 debuginfo 文件,通過 http 接口暴露給采集側(cè)。debuginfo 文件可執(zhí)行文件 buildid 緩存,避免相同的可執(zhí)行文件被重復(fù)抽取。

4.3 采集側(cè)

采集側(cè)主要對(duì)各個(gè)Agent的性能采樣數(shù)據(jù)進(jìn)行集中采集和處理:

圖片

基本流程:

  • 服務(wù)發(fā)現(xiàn)并定時(shí)抓取所有eBPF Agent 的性能采樣數(shù)據(jù)
  • 抓取性能采樣數(shù)據(jù)后,對(duì)缺失名稱的函數(shù)地址進(jìn)行查找補(bǔ)足函數(shù)名
  • 如果是第一次遇到的可執(zhí)行文件,異步下載與構(gòu)造符號(hào)索引,在索引 ready 之前始終忽略當(dāng)前采樣,直接返回;
  • 如果索引可用,先在 dwarf 中查找當(dāng)前地址關(guān)聯(lián)的函數(shù)和內(nèi)聯(lián)函數(shù),以及所屬文件和行號(hào);
  • 如果當(dāng)前地址不在 dwarf 的范圍里,回退到 symtab 中查找函數(shù)名稱
  • 符號(hào)關(guān)聯(lián)完成后我們就拿到了完整的生成火焰圖所需的所有數(shù)據(jù),這份數(shù)據(jù)我們生成并上傳火焰圖供性能平臺(tái)訪問,并且寫入 ck 存儲(chǔ)支持性能對(duì)比與性能退化監(jiān)測(cè)能力。

符號(hào)信息有2種來源,一種是可執(zhí)行文件的 .symtab 段, 提供了函數(shù)地址到函數(shù)名的簡(jiǎn)單映射,但是不包含內(nèi)聯(lián)函數(shù)信息。symtab 形式的的符號(hào)查找非常簡(jiǎn)單,地址和函數(shù)名一一對(duì)應(yīng)。

另一種是查找 dwarf 信息, .debug_xxx 段包含了每個(gè)函數(shù)覆蓋的指令范圍,函數(shù)名稱,調(diào)用了哪些內(nèi)聯(lián)函數(shù),屬于哪個(gè)文件,行號(hào)等豐富信息。

下面重點(diǎn)介紹dwarf的結(jié)構(gòu)和符號(hào)查找過程。

4.3.1. dwarf 結(jié)構(gòu)&符號(hào)查找

如下圖所示,結(jié)合一個(gè)例子,我們具體介紹下dwarf的結(jié)構(gòu)和符合查找過程:

圖片


結(jié)構(gòu):

dwarf 是ELF文件的debug_info section,用來表示源碼結(jié)構(gòu)信息,整體是樹狀結(jié)構(gòu),由DIE(debug info entry) 構(gòu)成,每個(gè) DIE 有Tag 字段來區(qū)分類型,且各自帶有不同屬性信息。

最外層的 DIE 表示代碼文件(DW_TAG_compile_unit,cu),如上圖中的server.c。文件下層的DIE是各種數(shù)據(jù)結(jié)構(gòu)、函數(shù)的聲明,其中我們主要關(guān)心兩種類型:函數(shù)(DW_TAG_subprogram) 與內(nèi)聯(lián)函數(shù)(DW_TAG_inlined_subroutine),內(nèi)聯(lián)函數(shù)處于調(diào)用它的函數(shù)下層,在上圖中都有所展示。

此外,文件、函數(shù)、內(nèi)聯(lián)函數(shù),都有屬性來代表指令范圍(如上圖中的[0x40000,0x500000])。指令范圍指的是,源代碼編譯生成的機(jī)器指令,在 .text 代碼段中的偏移范圍。函數(shù)編譯生成的機(jī)器指令分布不一定是連續(xù)的一段,可能由多段范圍構(gòu)成,查找時(shí)每段都參與匹配。內(nèi)聯(lián)函數(shù)的生成的指令是調(diào)用它的函數(shù)的一部分,它的指令范圍被它的 caller 函數(shù)覆蓋。

我們會(huì)使用這個(gè)屬性與待查找的指令地址做匹配。

查找過程:

查找函數(shù)的邏輯類似addr2line:

  • 定位文件DIE:對(duì)給定的 pc (指令地址),定位文件DIE (指令范圍包含此地址的);
  • 文件DIE的子節(jié)點(diǎn)遍歷:指令范圍包含地址的原則,對(duì)給定的pc,在各個(gè)子節(jié)點(diǎn)中進(jìn)行遍歷和查找;得到所有匹配的結(jié)點(diǎn),包括內(nèi)聯(lián)函數(shù)的結(jié)點(diǎn);
  • 內(nèi)聯(lián)函數(shù):函數(shù) DIE 提供了屬性可獲取函數(shù)名稱、所屬文件與行號(hào),內(nèi)聯(lián)函數(shù)不包含這些信息,需要通過 DW_AT_abstrct_origin 屬性 (如上圖中綠色序號(hào))找到原始函數(shù)聲明。

將所有函數(shù)信息按調(diào)用層級(jí)返回即完成查找,輸出函數(shù)調(diào)用鏈信息。整個(gè)過程中,文件DIE的定位和子結(jié)點(diǎn)遍歷如上圖中pc和藍(lán)色結(jié)點(diǎn)所示;內(nèi)聯(lián)函數(shù)的定位如圖中綠色地址的對(duì)應(yīng)關(guān)系所示。

4.3.2. 查找優(yōu)化

4.3.2.1. dwarf 索引

dwarf 符號(hào)查找的整個(gè)查找過程需要加載整個(gè) dwarf 結(jié)構(gòu),對(duì)于復(fù)雜項(xiàng)目來說,文件數(shù)量多且空間大。支持查找需要耗費(fèi)很多內(nèi)存,跳轉(zhuǎn)過程也較為復(fù)雜。對(duì)于執(zhí)行一次性命令的工具適合這種方法,對(duì)于持續(xù)常態(tài)化運(yùn)行的服務(wù)來說就比較浪費(fèi)。

為了保證查找的性能、節(jié)省內(nèi)存,我們將 dwarf 的內(nèi)容先做一次讀取,取出我們關(guān)心的信息來構(gòu)造成索引,后續(xù)的查找就可以基于索引來,這樣 dwarf 結(jié)構(gòu)就可以從內(nèi)存中釋放。索引構(gòu)建的過程分兩步:

  • 構(gòu)建全量的函數(shù)信息集合
  • 對(duì) dwarf 進(jìn)行深度優(yōu)先遍歷,取出每個(gè)遇到的函數(shù)和內(nèi)聯(lián)函數(shù)的信息,包括函數(shù)名、文件名、指令范圍等屬性,如下圖藍(lán)色部分所示;
  • 遍歷完成后,對(duì)內(nèi)聯(lián)函數(shù)查找函數(shù)定義,補(bǔ)足信息
  • 構(gòu)建地址范圍索引
  • 獲取所有函數(shù)的指令范圍屬性,每段指令范圍關(guān)聯(lián)到函數(shù)數(shù)組的下標(biāo);

  • 對(duì)范圍進(jìn)行排序:首先比較每個(gè)范圍的開始地址,這一步是為了支持函數(shù)地址的二分查找;如果開始地址相同(如一個(gè)函數(shù)的第一行就是執(zhí)行一個(gè)內(nèi)聯(lián)函數(shù)),比較它們的樹層級(jí)(子函數(shù)的層級(jí)更深),這一步是為了讓函數(shù)和內(nèi)聯(lián)函數(shù)可以直接按調(diào)用順序返回,得到正確的調(diào)用關(guān)系。如圖中黃色部分(ranges)所示

排序后的范圍數(shù)組和函數(shù)數(shù)組即可作為索引查找。

圖片

對(duì)于一個(gè)指令(上圖中pc)來說,在上文4.3.1中常規(guī)的查找過程,需要遍歷整個(gè)樹,同時(shí)全量的dwarf都被加載到內(nèi)存中并一直持有。我們優(yōu)化后的過程,對(duì)于一個(gè)指令,首先在ranges中,使用二分查找找到匹配的指令范圍;再根據(jù)關(guān)聯(lián)關(guān)系,得到對(duì)應(yīng)的函數(shù)信息;最后根據(jù)排序先后,得到函數(shù)調(diào)用鏈。優(yōu)化之后的過程中,僅依賴少量的屬性,大幅降低內(nèi)存使用量;并且基于索引信息,加快查找速度。

由于有些函數(shù)會(huì)在多個(gè)文件聲明,這一步完成后可能會(huì)匹配到多個(gè)名稱一樣的函數(shù)。我們將找到的函數(shù)按所屬的 cu 分組,選擇 cu offset最小的那組函數(shù),這個(gè)行為對(duì)齊了 llvm-addr2line 中的選擇入口 cu 的邏輯。

4.3.2.2. 符號(hào)緩存

長(zhǎng)時(shí)間運(yùn)行的服務(wù)采樣得到的函數(shù)地址有固定范圍,適合緩存,我們?cè)诜?hào)索引前加了一道緩存后,穩(wěn)定運(yùn)行情況下緩存命中率達(dá)到了 99.9%,緩存后的索引變?yōu)榘葱璨檎遥蠓档土瞬杉?wù)的 CPU 開銷。我們使用了可持久化的緩存保證服務(wù)重啟升級(jí)時(shí)的緩存命中率。

4.3.3. 存儲(chǔ)

在地址關(guān)聯(lián)后,我們得到了完整的Profiling Sample數(shù)據(jù)。我們對(duì)Profiling數(shù)據(jù),以Pod粒度進(jìn)行處理:如根據(jù)函數(shù)調(diào)用鏈統(tǒng)計(jì) Sample 數(shù)、過濾占比過低的函數(shù)調(diào)用鏈等。處理后,我們將數(shù)據(jù)進(jìn)行存儲(chǔ),用于后續(xù)的分析。我們的存儲(chǔ)方案選擇的是 Clickhouse,在存儲(chǔ) Profiling 的數(shù)據(jù)之外,同時(shí)會(huì)把相關(guān)的環(huán)境變量信息一起存儲(chǔ),如應(yīng)用名、應(yīng)用版本、機(jī)房等。

此外,根據(jù)Profiling Sample,當(dāng)前會(huì)一起生成單 Pod 的火焰圖,將火焰圖壓縮并保存在對(duì)象存儲(chǔ)中。

4.4 產(chǎn)品化 & 落地場(chǎng)景

4.4.1 實(shí)時(shí)火焰圖

提供 C++ 服務(wù)各個(gè) Pod 、各歷史版本的近實(shí)時(shí)火焰圖,例子如下:

圖片

圖片

后續(xù)基于 Clickhouse 存儲(chǔ)可實(shí)現(xiàn)實(shí)時(shí)的火焰圖查詢,實(shí)現(xiàn)任意范圍的火焰圖生成和展示。

4.4.2 性能對(duì)比分析

對(duì)于接入的服務(wù),提供當(dāng)前、歷史版本之間的性能diff分析,無須人工對(duì)比火焰圖,例子如下:

圖片

在性能diff火焰圖中,展示潛在的性能退化點(diǎn)的調(diào)用鏈、對(duì)應(yīng)的資源漲幅情況:

圖片

4.4.3.性能退化監(jiān)測(cè)

當(dāng)前支持對(duì)接入的服務(wù),進(jìn)行天級(jí)別的自動(dòng)化性能退化巡檢并推送。

圖片

當(dāng)前已經(jīng)接入推薦排序服務(wù)、以及廣告業(yè)務(wù)線的C++服務(wù)。上線近一個(gè)月以來,發(fā)現(xiàn)多起疑似性能退化的Case,已經(jīng)反饋給業(yè)務(wù)方并跟進(jìn)排查中。

05總結(jié)與展望

在過去的半年時(shí)間內(nèi),我們從零開始,嘗試將eBPF技術(shù)與可觀測(cè)的實(shí)際需求結(jié)合,來解決之前的一些疑難問題,比如流量來源分析、C++服務(wù)的Profiling等。這些能力在推薦、廣告、Redis、Redkv等業(yè)務(wù)線的核心服務(wù)中得到了應(yīng)用,接入服務(wù)過千,覆蓋近五萬個(gè)Node,實(shí)現(xiàn)日常常態(tài)化的運(yùn)行。

在當(dāng)前基礎(chǔ)上,未來我們計(jì)劃在以下方面繼續(xù)演化:

  • 流量分析:支持繪制服務(wù)拓?fù)洌a(bǔ)充 C++ 等多語言拓?fù)渑c鏈路數(shù)據(jù);
  • Profiling的應(yīng)用上:支持Off-CPU、內(nèi)存泄露排查等更多的事件類型;支持實(shí)時(shí)的火焰圖查詢,實(shí)現(xiàn)任意范圍的火焰圖生成和展示。

06作者簡(jiǎn)介

  • 韓柏
    小紅書可觀測(cè)技術(shù)工程師,畢業(yè)于上海交通大學(xué),從事推薦架構(gòu)、基礎(chǔ)架構(gòu)工作,在可觀測(cè)、云原生、推薦工程、中間件、性能優(yōu)化等方面有較為豐富的經(jīng)驗(yàn)。


  • 布克
    小紅書可觀測(cè)技術(shù)工程師,畢業(yè)于南京大學(xué),從事基礎(chǔ)架構(gòu)可觀測(cè)相關(guān)工作,熟悉監(jiān)控基礎(chǔ)組件、時(shí)序數(shù)據(jù)庫相關(guān)的研發(fā)。


  • 科米
    小紅書可觀測(cè)技術(shù)工程師,畢業(yè)于浙江大學(xué),從事基礎(chǔ)架構(gòu)可觀測(cè)相關(guān)工作,熟悉可觀測(cè)日志、指標(biāo)相關(guān)工作。
責(zé)任編輯:龐桂玉 來源: 小紅書技術(shù)REDtech
相關(guān)推薦

2023-10-13 13:40:29

2024-10-23 20:09:47

2024-10-10 08:19:50

2022-05-10 08:27:15

小紅書FlinkK8s

2021-09-14 09:52:56

ToB小程序生態(tài)評(píng)估

2021-11-19 09:40:50

數(shù)據(jù)技術(shù)實(shí)踐

2021-06-23 10:00:46

eBPFKubernetesLinux

2022-09-08 10:08:31

阿里云可觀測(cè)云原生

2023-11-17 08:00:54

Tetragon執(zhí)行工具

2021-12-01 00:05:03

Js應(yīng)用Ebpf

2024-06-19 07:45:20

2023-05-18 22:44:09

2021-05-24 15:48:38

高德打車系統(tǒng)可觀測(cè)性

2023-10-09 14:15:52

可觀測(cè)性數(shù)據(jù)

2015-09-10 13:28:51

暢享網(wǎng)

2022-08-30 08:22:14

可觀測(cè)性監(jiān)控軟件

2023-09-20 16:11:32

云原生分布式系統(tǒng)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久99蜜桃综合影院免费观看 | 天天天天天天天干 | 亚洲一区二区免费视频 | 综合久久亚洲 | 狠狠色香婷婷久久亚洲精品 | 欧美日韩在线一区二区 | av在线影院| 成人午夜精品 | 精品国产乱码久久久久久图片 | 成人小视频在线观看 | 81精品国产乱码久久久久久 | 亚洲人精品午夜 | 国产午夜精品一区二区三区在线观看 | 亚洲欧美在线视频 | 亚洲一区二区不卡在线观看 | 日韩福利在线观看 | 毛片入口 | 精品亚洲永久免费精品 | 四虎永久免费地址 | 九九免费视频 | 久久福利网站 | 日日操夜夜摸 | 日韩伦理一区二区 | 91社区在线观看 | 黄色免费网址大全 | 欧美日韩久久 | 成人不卡视频 | 在线国产一区二区 | 99精品视频免费观看 | 久草免费福利 | 亚洲欧美激情国产综合久久久 | 国产成人99久久亚洲综合精品 | 青青草在线播放 | 秋霞精品| 成人a视频片观看免费 | 91精品久久久久久久久久 | 亚洲一区二区三区视频 | 国产三级国产精品 | 日韩精品一二三 | 69视频在线播放 | 久久久久久国产精品 |