原文鏈接:Fastrace: A Modern Approach to Distributed Tracing in Rust | FastLabs / Blog
摘要
在微服務架構中,分布式追蹤對于理解應用程序的行為至關重要。雖然 tokio-rs/tracing 在 Rust 中被廣泛使用,但它存在一些顯著的挑戰:生態系統碎片化、配置復雜以及高開銷。
Fastrace 提供了一個可用于生產環境的解決方案,具有無縫的生態系統集成、開箱即用的 OpenTelemetry 支持,以及更簡潔的 API,能夠自然地與現有的日志基礎設施協同工作。
以下示例展示了如何使用 fastrace 對函數進行追蹤:
#[fastrace::trace]
pub fn send_request(req: HttpRequest) -> Result<(), Error> {// ...
}
Fastrace 已在如 ScopeDB 等產品中投入生產使用,幫助追蹤和調試 PB 級的可觀測性數據工作負載。
為什么分布式追蹤很重要
在當今的微服務和分布式系統中,了解應用程序內部發生的事情變得前所未有地困難。一個用戶請求可能在完成之前涉及數十個服務,傳統的日志記錄方法很快就會顯得不足。
考慮一個典型的請求流程:
用戶 → API 網關 → 認證服務 → 用戶服務 → 數據庫
當發生異常或應用程序性能不佳時,問題究竟發生在哪里?單個服務的日志只顯示了追蹤的片段,缺乏整個系統中請求流動的關鍵上下文。
這使得分布式追蹤變得至關重要。追蹤創建了一個跨服務邊界的請求流程的連接視圖,使得能夠:
- 識別跨服務的性能瓶頸
- 調試組件之間的復雜交互
- 了解依賴關系和服務關系
- 分析延遲分布和異常值
- 將日志和指標與請求上下文相關聯
一個常見的方案:tokio-rs/tracing
對于一些 Rust 開發者來說,tokio-rs/tracing 是實現追蹤的首選解決方案。以下是一個典型例子:
fn main() {// 初始化 tracing 訂閱者// 省略復雜的配置代碼...// 創建一個 span 并記錄一些數據let span = tracing::info_span!("processing_request",user_id = 42,request_id = "abcd1234");// 進入 span(為當前執行上下文激活它)let _guard = span.enter();// 在 span 上下文中記錄日志tracing::info!("Starting request processing");process_data();tracing::info!("Finished processing request");
}
tokio-rs/tracing 提供了過程宏簡化函數插樁:
#[tracing::instrument(skip(password), fields(user_id = user.id))]
async fn authenticate(user: &User, password: &str) -> Result<AuthToken, AuthError> {tracing::info!("Authenticating user {}", user.id);// ...更多代碼...
}
tokio-rs/tracing 的問題
根據我們的用戶體驗,tokio-rs/tracing 存在幾個顯著的問題:
1. 生態系統碎片化
tokio-rs/tracing引入自己的日志宏,與使用標準 log crate 的代碼產生了分歧:
// 使用 log crate
log::info!("Starting operation");
// 使用 tracing crate(不同的語法)
tracing::info!("Starting operation");
這種碎片化對庫作者尤其成問題。在創建庫時,作者將面臨一個困難的選擇:
- 使用 log crate,以兼容更廣泛的生態系統
- 使用 tokio-rs/tracing,以獲得更好的可觀測性功能
許多庫為了簡單選擇了第一種方式,但錯過了追蹤的好處。
雖然 tokio-rs/tracing 提供了一個 log 特性標志,允許在使用 tokio-rs/tracing 的宏時向 log crate 發出日志記錄,但庫作者必須手動啟用這個特性,以確保所有用戶無論使用哪種日志框架都能正確接收日志記錄。這為庫維護者帶來了額外的配置復雜性。
此外,使用 tokio-rs/tracing 的應用程序還必須安裝和配置 tracing-log 橋接器,以正確接收使用 log crate 的庫的日志記錄。這造成了一個需要顯式配置的雙向兼容性問題:
# Library's Cargo.toml
[dependencies]
tracing = { version = "0.1", features = ["log"] } # Emit log records for log compatibility# Application's Cargo.toml
[dependencies]
tracing = "0.1"
tracing-log = "0.2" # Listen to log records for log compatibility
2. 對庫的性能影響
庫的作者對性能開銷特別敏感,因為他們的代碼可能會在循環或性能關鍵路徑中被調用。當使用 tokio-rs/tracing 進行檢測時,其開銷可能相當顯著,這帶來了一個兩難的選擇:
- 始終進行追蹤檢測?—— 這樣會對所有用戶都帶來額外的性能開銷。
- 完全不進行檢測?—— 這樣會失去可觀測性。
- 創建一個額外的特性標志系統?—— 增加維護成本和復雜度。
以下是使用 tokio-rs/tracing 的庫中常見的模式:
#[cfg_attr(feature = "tracing", tracing::instrument(skip(password), fields(user_id = user.id)))]
async fn authenticate(user: &User, password: &str) -> Result<AuthToken, AuthError> {// ...更多代碼...
}
不同的庫可能會定義稍有差異的特性名稱,這使得最終的應用程序在配置這些標志時變得十分復雜。
對于 tokio-rs/tracing 來說,目前并沒有一種干凈的方式來實現“零成本的禁用”(zero-cost disabled)。這導致庫的作者不愿意在性能敏感的代碼路徑中添加檢測邏輯。
3. 不支持上下文傳播
分布式追蹤要求在服務邊界之間傳播上下文信息,但 tokio-rs/tracing 大部分情況下將這個任務留給了開發者來手動處理。例如,下面是 tonic 官方提供的 gRPC 服務追蹤示例:
Server::builder().trace_fn(|_| tracing::info_span!("grpc_server")).add_service(MyServiceServer::new(MyService::default())).serve(addr).await?;
上述示例僅僅創建了一個基礎的 span,但是并沒有從傳入的請求中提取追蹤上下文。
在分布式系統中,缺乏上下文傳播會導致嚴重的后果。當由于上下文缺失而導致追蹤斷開時,你將無法看到完整的請求流,例如:
期望的完整追蹤流:
Trace #1: 前端 → API 網關 → 用戶服務 → 數據庫 → 響應
實際看到的卻是斷開的片段:
Trace #1: 前端 → API 網關
Trace #2: 用戶服務 → 數據庫
Trace #3: API 網關 → 響應
更糟糕的是,當多個請求交錯執行時,這些追蹤片段會變得混亂:
Trace #1: 前端 → API 網關
Trace #2: 前端 → API 網關
Trace #3: 前端 → API 網關
Trace #4: 用戶服務 → 數據庫
Trace #6: API 網關 → 響應
Trace #5: 用戶服務 → 數據庫
這種碎片化會極大地增加跟蹤請求流、隔離性能問題以及理解服務之間因果關系的難度,影響調試和優化的效率。
引入 fastrace:一個快速而完整的解決方案
1. 零成本抽象(Zero-cost Abstraction)
fastrace 設計時采用了真正的零成本抽象。當禁用追蹤時,所有的追蹤代碼會在編譯期間被完全移除,因此不會產生任何運行時開銷。這使得它非常適合對性能要求敏感的庫使用。
2. 生態系統兼容性(Ecosystem Compatibility)
fastrace 專注于分布式追蹤,并通過可組合的設計與現有的 Rust 生態系統無縫集成,包括對標準 log crate 的支持。這種架構設計允許庫實現全面的追蹤功能,同時保留用戶選擇其偏好的日志設置的自由。
3. 簡潔優先(Simplicity First)
API 設計直觀且簡潔,減少了模板代碼的編寫,專注于最常見的使用場景,同時在需要時提供擴展能力。
4. 極致性能(Insanely Fast)
fastrace 為高性能應用而生,能夠處理大量的 span(追蹤片段),并且對 CPU 和內存的使用影響極小。
5. 應用與庫的雙重適配(Ergonomic for both Applications and Libraries)
fastrace 可以在不引入性能開銷的情況下被庫使用:
#[fastrace::trace] // 當未啟用 "enable" 特性時,是真正零成本的
pub fn process_data(data: &[u8]) -> Result<Vec<u8>, Error> {// 庫內部使用標準 log cratelog::debug!("Processing {} bytes of data", data.len());// ...更多代碼...
}
關鍵在于庫引入 fastrace 時,不需要開啟任何特性:
[dependencies]
fastrace = "0.7" # 不啟用 "enable" 特性
當應用程序使用該庫且沒有啟用 fastrace 的 “enable” 特性時:
- 所有追蹤代碼在編譯時會被完全優化掉
- 不會引入任何運行時開銷
- 對性能關鍵路徑沒有任何影響
而當應用程序啟用了 enable 特性時:
- 庫中的檢測邏輯會被激活
- span 會被收集并上報
- 應用能夠對庫的內部行為進行全面可視化
這種設計相較于傳統追蹤解決方案有顯著優勢,傳統方案通常會始終引入開銷,或者要求庫作者實現復雜的特性標志系統。
6. 無縫的上下文傳播(Seamless Context Propagation)
fastrace 提供了多種主流框架的集成庫,能夠自動處理上下文傳播:
- HTTP 客戶端(reqwest)
let response = client.get(&format!("https://user-service/users/{}", user_id)).headers(fastrace_reqwest::traceparent_headers()) // 自動注入追蹤上下文.send().await?;
- gRPC 服務端(tonic)
Server::builder().layer(fastrace_tonic::FastraceServerLayer) // 自動從請求中提取上下文.add_service(MyServiceServer::new(MyService::default())).serve(addr);
- gRPC 客戶端
let channel = ServiceBuilder::new().layer(fastrace_tonic::FastraceClientLayer) // 自動注入上下文到請求中.service(channel);
- 數據訪問(Apache OpenDAL)
let op = Operator::new(services::Memory::default())?.layer(opendal::layers::FastraceLayer) // 自動追蹤所有數據操作.finish();
op.write("test", "0".repeat(16 * 1024 * 1024).into_bytes()).await?;
通過這些集成,fastrace 實現了開箱即用的分布式追蹤,無需手動處理上下文傳播,大幅簡化了開發者的工作量。
完整的解決方案:fastrace + log + logforth
fastrace 專注于做好一件事:分布式追蹤。通過它的可組合設計以及對 Rust 生態的良好支持,與以下工具共同構建了一個強大的可觀測性解決方案:
- log:Rust 的標準日志接口,用于基礎日志記錄。
- logforth:具有工業級特性的靈活日志實現,支持更復雜的日志管理與調度。
- fastrace:高性能的分布式追蹤,支持上下文傳播和跨服務鏈路跟蹤。
這種集成可以讓日志自動關聯到追蹤 span,不需要額外切換不同的日志宏:
log::info!("Processing started");
在你的日志基礎設施中,你可以清楚地看到每個日志條目對應的追蹤 ID 和 span,便于更高效的關聯和分析。
完整示例:構建一個具備完整可觀測性的微服務
以下是一個基于 fastrace、log 和 logforth 的簡潔微服務示例:
#[poem::handler]
#[fastrace::trace] // 自動創建并管理 span
async fn get_user(Path(user_id): Path<String>) -> Json<User> {// 標準日志會自動關聯到當前 spanlog::info!("Fetching user {}", user_id);let user_details = fetch_user_details(&user_id).await;Json(User {id: user_id,name: user_details.name,email: user_details.email,})
}
子任務的追蹤:
#[fastrace::trace]
async fn fetch_user_details(user_id: &str) -> UserDetails {let client = reqwest::Client::new();let response = client.get(&format!("https://user-details-service/users/{}", user_id)).headers(fastrace_reqwest::traceparent_headers()) // 自動傳播追蹤上下文.send().await.expect("Request failed");response.json::<UserDetails>().await.expect("Failed to parse JSON")
}
主服務的配置和啟動:
#[tokio::main]
async fn main() {// 配置日志和追蹤setup_observability("user-service");let app = poem::Route::new().at("/users/:id", poem::get(get_user)).with(fastrace_poem::FastraceMiddleware); // 自動提取追蹤上下文poem::Server::new(poem::listener::TcpListener::bind("0.0.0.0:3000")).run(app).await.unwrap();fastrace::flush();
}
日志與追蹤的初始化:
fn setup_observability(service_name: &str) {// 配置 logforth 進行日志管理logforth::stderr().dispatch(|d| {d.filter(log::LevelFilter::Info)// 將追蹤 ID 附加到日志.diagnostic(logforth::diagnostic::FastraceDiagnostic::default())// 將日志附加到 span.append(logforth::append::FastraceEvent::default())}).apply();// 配置 fastrace 進行分布式追蹤fastrace::set_reporter(fastrace_jaeger::JaegerReporter::new("127.0.0.1:6831".parse().unwrap(), service_name).unwrap(),fastrace::collector::Config::default());
}
總結
fastrace 代表了 Rust 中分布式追蹤的現代化解決方案,主要具備以下顯著優勢:
- 零運行時開銷(Zero Runtime Overhead When Disabled):
- 當應用未啟用追蹤時,庫中的檢測代碼會被完全優化掉,不會影響性能。
- 無生態鎖定(No Ecosystem Lock-In):
- 使用 fastrace 不會強制用戶依賴某一特定日志系統,可靈活適配 log、logforth 等多種實現。
- 簡單的 API 接口(Simple API Surface):
- 簡潔的 API 設計,讓開發者能夠輕松實現全面追蹤,而無需復雜的配置。
- 可預測的性能表現(Predictable Performance):
- 即使在高負載下,fastrace 的性能依舊穩定、可預測。
如果生態中的庫都能全面支持 fastrace,那么應用程序將擁有前所未有的可觀測性,而不必擔心性能損耗或兼容性問題。
相關資源
- https://github.com/fast/fastrace
- https://crates.io/crates/fastrace-jaeger
- https://crates.io/crates/fastrace-opentelemetry
- https://crates.io/crates/fastrace-reqwest
- https://crates.io/crates/fastrace-poem
- https://crates.io/crates/fastrace-tonic
- https://crates.io/crates/logforth
這一整套生態的組合,能夠讓你快速搭建高性能、易擴展、且可全面觀測的分布式系統。
觀測云的思考
性能對比:零成本抽象帶來的優勢
與傳統的 tokio-rs/tracing 相比,Fastrace 的零成本抽象(Zero-cost Abstraction)設計在未啟用時完全移除追蹤代碼,不會對運行時產生任何性能開銷。而 tokio-rs/tracing 即使在未采集數據的情況下,仍會有一定的性能損耗。
此外,Fastrace 的上下文傳播是自動化且無縫的,而 tokio-rs/tracing 則需要手動處理上下文,增加了復雜度和潛在的錯誤風險。
一站式解決方案:Fastrace + 觀測云
Fastrace 的強大分布式追蹤能力不僅能幫助開發者高效追蹤微服務調用鏈,還能夠無縫對接到觀測云平臺,實現更加全面的可觀測性監控。通過將 Fastrace 的 OpenTelemetry 數據直接接入觀測云,開發者可以在統一的平臺上實時查看鏈路追蹤、性能瓶頸以及跨服務的調用關系,大幅提升問題排查和性能優化的效率。無論是調試復雜的微服務系統,還是在生產環境中快速定位故障,該組合都能以較低的接入成本帶來卓越的性能監控體驗,真正實現“全鏈路可觀測,一站式可視化”。