Databend 全鏈路追蹤
全鏈路追蹤意味着能夠追蹤到每一個調用請求的完整調用鏈路、收集性能數據並反饋異常。Databend 使用 tracing 賦能可觀測性,實現全鏈路追蹤。
Databend 與 Tracing
Databend 與 Tracing
初步瞭解 Databend 怎麼實現全鏈路追蹤。
初識 Tracing
Tracing 是由 Tokio 團隊維護的 Rust 應用跟蹤框架,用來收集結構化的、基於事件的診斷信息。
項目地址:https://github.com/tokio-rs/tracing
示例:
use tracing::{info, Level};
use tracing_subscriber;
fn main() {
let collector = tracing_subscriber::fmt()
// filter spans/events with level TRACE or higher.
.with_max_level(Level::TRACE)
// build but do not install the subscriber.
.finish();
tracing::collect::with_default(collector, || {
info!("This will be logged to stdout");
});
info!("This will _not_ be logged to stdout");
}
use tracing::{info, Level};
use tracing_subscriber;
fn main() {
let collector = tracing_subscriber::fmt()
// filter spans/events with level TRACE or higher.
.with_max_level(Level::TRACE)
// build but do not install the subscriber.
.finish();
tracing::collect::with_default(collector, || {
info!("This will be logged to stdout");
});
info!("This will _not_ be logged to stdout");
}
Databend 中的 Tracing
Databend 的 tracing-subscriber 被統一整合在 common/tracing,由 query 和 meta 共用。
// Use env RUST_LOG to initialize log if present.
// Otherwise use the specified level.
let directives = env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_x| level.to_string());
let env_filter = EnvFilter::new(directives);
let subscriber = Registry::default()
.with(env_filter) # 根據環境變量過濾
.with(JsonStorageLayer) # 利用 tracing-bunyan-formatter 格式化爲 json
.with(stdout_logging_layer) # 標準輸出
.with(file_logging_layer) # 輸出到文件,默認位於 `_logs` 目錄下
.with(jaeger_layer); # opentelemetry-jaeger
#[cfg(feature = "console")]
let subscriber = subscriber.with(console_subscriber::spawn()); # tokio console
tracing::subscriber::set_global_default(subscriber)
.expect("error setting global tracing subscriber");
// Use env RUST_LOG to initialize log if present.
// Otherwise use the specified level.
let directives = env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_x| level.to_string());
let env_filter = EnvFilter::new(directives);
let subscriber = Registry::default()
.with(env_filter) # 根據環境變量過濾
.with(JsonStorageLayer) # 利用 tracing-bunyan-formatter 格式化爲 json
.with(stdout_logging_layer) # 標準輸出
.with(file_logging_layer) # 輸出到文件,默認位於 `_logs` 目錄下
.with(jaeger_layer); # opentelemetry-jaeger
#[cfg(feature = "console")]
let subscriber = subscriber.with(console_subscriber::spawn()); # tokio console
tracing::subscriber::set_global_default(subscriber)
.expect("error setting global tracing subscriber");
具體到內部的 tracing 記錄,大致有兩類:
- 普通:與其他 log 方式一樣,利用 info!、debug! 來收集信息。
use common_tracing::tracing;
tracing::info!("{:?}", conf);
tracing::info!("DatabendQuery {}", *databend_query::DATABEND_COMMIT_VERSION);
use common_tracing::tracing;
tracing::info!("{:?}", conf);
tracing::info!("DatabendQuery {}", *databend_query::DATABEND_COMMIT_VERSION);
- Instruments:在調用函數時創建並進入 tracing span(跨度),span 表示程序在特定上下文中執行的時間段。
use common_tracing::tracing::debug_span;
#[tracing::instrument(level = "debug", skip_all)]
async fn read(&mut self) -> Result<Option<DataBlock>> {
...
fetched_metadata = read_metadata_async(&mut self.reader)
.instrument(debug_span!("parquet_source_read_meta"))
.await
.map_err(|e| ErrorCode::ParquetError(e.to_string()))?;
...
}
use common_tracing::tracing::debug_span;
#[tracing::instrument(level = "debug", skip_all)]
async fn read(&mut self) -> Result<Option<DataBlock>> {
...
fetched_metadata = read_metadata_async(&mut self.reader)
.instrument(debug_span!("parquet_source_read_meta"))
.await
.map_err(|e| ErrorCode::ParquetError(e.to_string()))?;
...
}
示例:
{
"v": 0,
"name": "databend-query-test_cluster@0.0.0.0:3307",
"msg": "Shutdown server.",
"level": 30,
"hostname": "dataslime",
"pid": 53341,
"time": "2022-05-11T00:51:56.374807359Z",
"target": "databend_query",
"line": 153,
"file": "query/src/bin/databend-query.rs"
}
{
"v": 0,
"name": "databend-query-test_cluster@0.0.0.0:3307",
"msg": "Shutdown server.",
"level": 30,
"hostname": "dataslime",
"pid": 53341,
"time": "2022-05-11T00:51:56.374807359Z",
"target": "databend_query",
"line": 153,
"file": "query/src/bin/databend-query.rs"
}
觀測 Databend 追蹤的方式
Databend 原生提供了多種觀測方式,以方便診斷和調試:
-
http tracing :訪問 localhost:8080/v1/logs(根據配置)
-
stdout/filelog :檢查終端輸出或 _logs 目錄(根據配置)
-
system.tracing 表 :執行 select * from system.tracing limit 20;
-
jaeger :運行 jaeger ,訪問 http://127.0.0.1:16686/
-
console :按特定方式構建後,運行 tokio-console
Jaeger 分佈式追蹤
使用 Jaeger 對 Databend 進行全鏈路追蹤。
Opentelemetry & Jaeger
OpenTelemetry 是工具、API 和 SDK 的集合。使用它來檢測、生成、收集和導出遙測數據(度量、日誌和追蹤),以幫助您分析軟件的性能和行爲。
Jaeger 是一個開源的端到端分佈式追蹤系統。由 Uber 捐贈給 CNCF 。它可以用於監視基於微服務的分佈式系統,提供以下能力:
-
分佈式上下文傳播
-
分佈式事務監視
-
根本原因分析
-
服務依賴性分析
-
性能 / 延遲優化
Step by Step
遵循下述步驟,即可使用 Jaeger 探索 Databend :
-
構建二進制程式:cargo build(可以使用 --bin 指定)
-
將日誌級別設定爲 DEBUG ,運行需要調試的應用程式。例如,LOG_LEVEL=DEBUG ./databend-query
-
運行 jaeger :docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in- one:latest
-
打開 http://127.0.0.1:16686/ 以查看 jaeger 收集的信息
結果探索
x 軸是執行時刻,y 軸是持續的時間,圓點反映 span 的聚集程度。
執行下述語句即可得到上圖所示追蹤結果:
CREATE TABLE t1(a INT);
INSERT INTO t1 VALUES(1);
INSERT INTO t1 SELECT * FROM t1;
CREATE TABLE t1(a INT);
INSERT INTO t1 VALUES(1);
INSERT INTO t1 SELECT * FROM t1;
Timeline
下圖是點擊最大的圓點得到的追蹤情況:
使用 timeline 模式來展現 tracing 的各個跨度之間的關係。以時間爲主線進行分析, 方便使用者觀看在某個時間點觀看程序信息。
點開第一個跨度,可以看到這是執行 INSERT INTO t1 SELECT *FROM t1 查詢時的情況。
Graph
切換到 graph 模式,可以看到各個 span 之間的調用鏈,每個 span 具體用時 , 以及百分比。
通過這個視圖使用者很容易知道系統瓶頸, 快速定位問題。
Compare
連起來的各個部分形成整個 trace 的調用鏈。因爲比較時一般會比較兩個相同類型的調用,所以看到的會是重合後的視圖。
對於顏色的一個說明:
-
深綠色,表示這個 span 只存在於 trace-B 中,A 沒有這個 span
-
深紅色,表示這個 span 只存在於 trace-A 中,B 沒有這個 span
-
淺綠色,表示這個 span 在 trace-B(右邊這個)的數量多
-
淺紅色,表示這個 span 在 trace-A(左邊這個)的數量多
tokio-console 診斷
tokio-console 診斷
tokio-rs 團隊出品的診斷和調試工具,可以幫助我們診斷與 tokio 運行時相關的問題。
console 是什麼
tokio-console 是專爲異步程序設計的調試與診斷工具,能夠列出 tokio 的任務,提供對程序的任務和資源的實時、易於導航的視圖,總結了它們的當前狀態和歷史行爲。主要包含以下組件:
-
傳輸診斷數據的協議
-
用於收集診斷數據的儀器
-
當然,用於展示和探索診斷數據的實用工具
項目地址:https://github.com/tokio-rs/console
Step by Step
-
使用特定的 rustflags 和 features 來構建:RUSTFLAGS="--cfg tokio_unstable" cargo build --features tokio-console ,也可以只構建單個二進制程式,使用 --bin 進行指定。
-
將日誌級別設定爲 TRACE ,運行需要調試的應用程式 LOG_LEVEL=TRACE databend-query 或者 databend-meta --single --log-level=TRACE。可以使用 TOKIO_CONSOLE_BIND 指定端口,以避免潛在的端口搶佔問題。
-
運行 tokio-console,默認連接到 http://127.0.0.1:6669
結果探索
任務
先看什麼是 tokio 任務:
-
任務是一個輕量級的、非阻塞的執行單元。類似操作系統的線程,但是是由 tokio 運行時管理,一般叫做 “綠色線程”,與 Go 的 goroutine,Kotlin 的 coroutine 類似。
-
任務是協同調度的。大多數操作系統實現搶佔式多任務。操作系統允許每個線程運行一段時間,然後搶佔它,暫停該線程並切換到另一個線程。另一方面,任務實現協同多任務。一個任務被允許運行直到它讓出執行權,運行時會切換到執行下一個任務。
基礎視圖
通過左右切換,可以得到總忙時間或輪詢次數等指標對任務進行排序。控制檯通過高亮來提示較大差異,比如從毫秒到秒的切換。
控制檯還實現了一個 “警告” 系統。通過監視應用程序中任務的運行時操作,控制檯可以檢測可能提示 bug 或性能問題的行爲模式,並突出顯示這些行爲模式供用戶分析。比如已經運行了很長時間而沒有讓步的任務,喚醒的次數比被其他任務喚醒的次數還要多的任務。
任務視圖
上下切換選中任務,enter 查看關於每個任務的翔實數據,比如輪詢持續時間的直方圖。
不僅列出任務。console 還會插入異步互斥和信號量等資源。Tokio Console 的資源詳細信息視圖顯示了哪些任務已經進入臨界區,哪些任務正在等待獲得訪問權限。
還能做什麼
還能做什麼
與分佈式追蹤和日誌系統相關的一些其他內容。
可觀測性 + +
目前還有一系列關於可觀測性和 Tracing 的 Issue 有待解決:
-
Integrate tokio metrics for query task based tokio runtime monitoring #4205
-
Configure on jaeger tracing address similar to metrics api server #3633
-
Summary of todos about distributed tracing #1227
-
Query traces and analysis, based on user behavior. #1177
-
Http stack traces #1085
-
Shuffle read/write metrics #1004
另外,更進一步的考量是,如何基於可觀測性來自動 / 半自動地發現問題並對系統進行調優。
瞭解更多
瞭解更多
Tracing
-
https://github.com/tokio-rs/tracing
-
https://docs.rs/tracing/latest/tracing/
-
https://tokio.rs/blog/2019-08-tracing
Jaeger
-
https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger
-
https://21-lessons.com/ship-rust-tracing-spans-to-jaeger/
tokio-console
-
https://github.com/tokio-rs/console
-
https://hackmd.io/@aws-rust-platform/ByChcdB-t
-
https://tokio.rs/blog/2021-12-announcing-tokio-console
關於 Databend
Databend 是一款開源、彈性、低成本,基於對象存儲也可以做實時分析的新式數倉。期待您的關注,一起探索雲原生數倉解決方案,打造新一代開源 Data Cloud。
-
Databend 文檔:https://databend.rs/
-
Twitter:https://twitter.com/Datafuse_Labs
-
Slack:https://datafusecloud.slack.com/
-
Wechat:Databend
-
GitHub :https://github.com/datafuselabs/databend
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/NJi92jg2Egoi-kK3HFXPEA