brpc 的 bvar 效能採集庫學習筆記
brpc的bvar效能採集庫-學習筆記
brpc 是百度開源的rpc框架,具有效能卓越、設計獨到、實現優雅和協議豐富的特點
由於我做過效能採集和監控相關的工作,因此對bvar的實現非常感興趣,通過精讀程式碼,學習了bvar的實現細節,這裡做個記錄。個人水平有限,有錯誤的地方請指正。
bvar 是一個相對獨立的元件,不依賴外部,你可以只使用bvar而不用將整個brpc庫接入。bvar的接入使用也十分簡單,官方文件也很全面,通過本文也是想讓讀者有個直觀的認識,直接上手。
bvar的基本特性
最簡單來說,想象有一個累加器型別的bvar,它將有兩個成員,一個是name,另一個是統計值,儲存統計值的記憶體空間是從本型別的tls記憶體池申請而來;它還有一個方法,將每次的新值累加起來,bvar物件過載***operator<<***運算子,接受並聚合統計值,類似於“寫”語義。
bvar支援執行緒安全的數值統計與採集,並且屬於寫遠大於讀的應用場景;它從tls記憶體池獲取記憶體,聚合本執行緒的統計值;在每次被讀取時再將統計數值聚合起來,相當於將寫操作的同步開銷轉移到讀操作,所以效能損耗少。
bvar中, Variable 是基類,派生出 Reducer 、 IntRecorder 、 Status 等子類, Window 也由 Variable 派生,他們共有行為主要有:
- expose :向程序全域性VarMap註冊自己,以expose的name作為索引。後臺dump執行緒遍歷VarMap讀值
- hide :與expose相反,從VarMap登出
- describe :描述Variable的名字和值,以字串返回
此外, LatencyRecorder 將其它型別組合而成,功能十分強大(具有類似於FIO的長尾IO分佈功能,99%、99.5%%、99.9%、99.99%,以及CDF,這個功能我覺得在執行時不好實現),本篇暫不包含它。
bvar型別與Variable可看作語義相同。
bvar的型別
- Reducer 派生出 Adder 、 Maxer 、 Miner 等子類,用於簡單的聚合、極大值極小值等監控項,Reducer的語義是聚合。
- IntRecorder 用於記錄一段時間內的均值
- Status 僅儲存數值或變數,比如某個變數的值,不同執行緒修改後立即可見; PassiveStatus 的特點是被動讀取,有需求時才讀值
- LatencyRecorder 實現 時延類 效能採集
bvar各型別擁有自己的聚合方法 Op 和 InvOp ,在模板中使用,比如Adder的Op是 bvar::detail::AddTo , InvOp是 MinusFrom ;在此之上,還要考慮到如何 採集 聚合結果,因此bvar提出 Sampler 來取樣。
bvar的採集
Sampler的語義是取樣,對Variable在某時刻或時段做讀值或者快照操作;bvar將Variable的ReducerSampler成員掛載在SamplerCollector的佇列中,啟動後臺執行緒,定期遍歷佇列,驅動Sampler採集Variable的值。(針對Reducer、IntRecorder和Window型別)
可以列舉以下場景:
- 每秒讀一次Variable的聚合結果,得到某指標的秒級資料
- 對Variable設定window,得到一個時間段內的聚合結果,在視窗內求最大值、最小值、平均值等
- 記錄現在到之前一分鐘內每秒的平均值
- 記錄最近60s內的秒級結果,最近60分鐘內的分鐘級結果,最近24小時內的小時級結果,最近30天內的天級結果,將這些取樣結果維持在一個序列裡
- 針對場景1
ReducerSampler
型別對Variable做每秒取樣,後臺執行緒呼叫 ReducerSampler::take_sample()
方法,驅動Reducer呼叫 get_value()
等將各執行緒的tls值聚合出結果,將結果存入自身確定大小的 BoundedQueue
中,該Queue結構使用類似於ringbuffer。另外, get_value()
也可能會在bvar_dump執行緒中呼叫,總之是用於讀取Variable的當前聚合結果。
- 針對場景2
說到Window,通俗來講就是設定一個視窗,將歷史值存起來,存就存在Variable的Sampler的Queue中。因此Queue size的可以增大。當然,大部分Variable使用時不考慮Window方式,因此size為1。 舉例說明,對一個 bvar::Maxer<int> max
,獲取從現在到之前1秒內的最大值,可以設定視窗為1,即 bvar::Window<bvar::Maxer<int>> max_per_second(&max, 1)
,獲取從現在到之前1分鐘內的最大值,設定視窗60,即 bvar::Window<bvar::Maxer<int>> max_per_minute(&max, 60)
。 bvar::Window
的預設視窗大小是 bvar_dump_interval
,預設10s,使用者可自定義。視窗大小將設定ReducerSampler的 BoundedQueue
的大小,且只會增大,不會縮小window。
Window的使用是非常重要的,很多概念藉助於它,設想我們要統計流量,首選是 bvar::Adder<uint64_t>
,由於Adder一直是累加的,我們拿到的是累計值。如果我們使用Window,則可以得到一段時間內的流量,因此Window型別的 get_value(time_t window_size)
方法是要指定時間維度的。那麼想獲得一段時間內的每秒流量呢?這就是場景3.
- 針對場景3
另一種Window的用法是,計算Variable的一段時間內每秒的平均值,型別為 bvar::PerSecond<>
。比如 bvar::PerSecond<bvar::Adder<int>> average_per_second(&adder, 60)
可以得到最近一分鐘內的每秒平均值。
Window和PerSecond的使用場景需要多體會,建議多參考 官方文件
- 針對場景4
SeriesSampler將Variable取樣的值存成序列,內部Series分配60+60+24+30大小的空間,用於儲存秒、分、時和天的取樣值,這些聚合值是效能監控最在意的點,極大提高了程式的可觀察性。將效能資料聚合工作放在執行時做,用很小的開銷實現即時觀察功能,試想這部分工作如果放到後臺做,需要用flink等平臺做聚合,會需要多少臺機器,將產生多大成本?還有些效能指標,如果根據同樣思路,通過例項單位聚合成程序的,程序的聚合成機器的,也可以省去後端聚合的工作。
SeriesSampler和ReducerSampler用途不同,所以理解時可以先把SeriesSampler放下。
瞭解這些語義後,可以通過bvar的單測來使用它。
bvar的使用示例
初次接觸brpc,跑最簡單的echo_c++例程,我就驚豔於brpc完善的監控項,並且在程序中內建http介面,能直接從瀏覽器讀到程序實時更新的各項指標,這就是把活做到位了。如下圖所示,圖一為echo_server的status監控,圖二為echo_server的bvar監控

圖一 echo_server的status

圖二 echo_server的bvar指標
在沒有brpc server的情況下,brpc提供一個 brpc::StartDummyServerAt(8080/*port*/);
開啟監控埠,因此我們在無brpc server的時候也可以享受這一便利。
bvar模組設計很獨立,使用非常簡單,可以單獨使用。通過 FLAGS_bvar_dump
和 FLAGS_bvar_dump_interval
配置bvar是否dump到檔案以及頻率,通過 FLAGS_bvar_log_dumpped
配置是否列印在日誌中;另外就是http介面。
得益於bvar的使用文件和單測用例,我們可以寫一個最小demo,如下所示。
- 開啟
http://localhost:8080/vars
可以觀察到自己定義的bvar指標 - 在本目錄下的monitor目錄中,adder、adderWindow和adderPersecond在檔案
monitor/bvar.demo_bvar.data
中,其餘bvar,比如a_latency
是dump在monitor/bvar.demo_bvar.latency.data
中,bvar會根據變數名將bvar分流。
#include "bvar/detail/agent_group.h" #include "bvar/bvar.h" #include "butil/time.h" #include "bvar/reducer.h" #include "bvar/passive_status.h" #include "brpc/server.h" #include "gflags/gflags.h" void dump() { bvar::Adder<int> a1("a_latency"); bvar::Adder<int> a2("a_qps"); bvar::Adder<int> a3("a_error"); bvar::Adder<int> a4("process_*"); bvar::Adder<int> a5("default"); bvar::Adder<int> c("demo"); bvar::Adder<int> adder("adder"); bvar::Window<bvar::Adder<int>> adder_window("adderWindow", &adder, 3); bvar::PerSecond<bvar::Adder<int>> adder_persecond("adderPersecond", &adder, 1); int cnt = 0; while(cnt++ < 1000) { c << 1 << 2 << 3; adder << 1000 - cnt; adder << 1000 - cnt-cnt; sleep(1); } } int main(int argc, char *argv[]) { GFLAGS_NS::SetCommandLineOption("bvar_dump_interval", "1"); GFLAGS_NS::SetCommandLineOption("bvar_dump", "true"); brpc::StartDummyServerAt(8080/*port*/); dump(); }
export LD_LIBRARY_PATH=/usr/local/lib g++ -g -O0 var.cpp -lbrpc -std=c++11 -I/pathto/brpc/output/include/ -lpthread -lgflags -DGFLAGS_NS=google -o demo_bvar
瀏覽器開啟 http://localhost:8080/vars

圖三 demo的bvar指標
最後
bvar是一個性能高、功能全、設計好的效能採集庫,它的設計和實現值得深入學習,尤其是把事情做到位的精神。
通讀程式碼的過程中,我能感受到brpc在實現的過程盡力的滿足需求,基本沒有拒絕過需求。江海不擇細流,故能成其大,實現使用者需求的過程中豐富基礎設施的功能,然後接更多的需求,這是一個正向反饋的過程,這種態度也是值得鼓勵學習的。