一个HTTP POST
请求过来,将看板上的卡片从“待处理”移到“进行中”,接口返回了200 OK
。但故事到这里就结束了吗?在真实的生产环境中,这恰恰是问题的开始。这次状态变更到底花了多少毫秒?其中数据库锁占用了多久?校验逻辑、状态更新和事件通知,哪一步是潜在的瓶лод?如果下游的通知服务出现了延迟,我们能快速定位到是网络问题还是下游服务本身的问题吗?如果不能回答这些问题,我们的服务就像一个黑盒,稳定运行全凭运气。
这次复盘的目标,就是从零开始构建一个具备深度可观测性的看板(Kanban)核心状态机服务。我们将使用 Rust 和 Rocket 框架,但重点不在于实现一个功能齐全的看板应用,而在于将 OpenTelemetry 的可观测性能力,像血管一样植入到服务的每一个角落。我们追求的不是事后补救式的监控,而是在设计之初就将“可观测性”作为一等公民的开发模式(Observability-Driven Development)。
第一步:奠定可观测性的基石
在编写任何业务逻辑之前,首先要搭建好整个可观测性基础设施。在 Rust 生态中,这通常意味着 tracing
crate 作为前端 API,与 opentelemetry
crate 生态作为后端实现相结合。
Cargo.toml
中的依赖是我们的第一份蓝图:
[dependencies]
rocket = { version = "0.5.0", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.20", features = ["full"] }
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid"] }
uuid = { version = "1.4", features = ["v4", "serde"] }
# Tracing & Observability Stack
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
opentelemetry = { version = "0.21", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "0.14", features = ["grpc-tonic"] }
tracing-opentelemetry = "0.22"
opentelemetry-semantic-conventions = "0.12"
这里的核心是 tracing
,tracing-subscriber
,opentelemetry
,opentelemetry-otlp
和 tracing-opentelemetry
。它们的职责分工明确:
-
tracing
: 提供一个统一的 API(如span!
、info!
宏和#[instrument]
属性)来在代码中埋点。 -
tracing-subscriber
: 收集tracing
产生的数据,并决定如何处理它们(例如,打印到控制台或发送到远端)。 -
opentelemetry
: OpenTelemetry 标准的 Rust 实现,定义了 Trace, Metric, Log 的数据模型。 -
opentelemetry-otlp
: 负责将 OpenTelemetry 数据通过 OTLP 协议导出到兼容的收集器(如 Jaeger, Grafana Tempo, Uptrace)。 -
tracing-opentelemetry
: 这是一个关键的“胶水层”,它将tracing
crate 产生的 Span 数据转换成 OpenTelemetry 的数据格式。
我们的第一个代码文件不是 main.rs
,而是 src/telemetry.rs
。初始化全局的 Tracer Provider 是任何可观测性实践的起点。
```rust
// src/telemetry.rs
use opentelemetry::runtime::Tokio;
use opentelemetry::sdk::propagation::TraceContextPropagator;
use opentelemetry::sdk::trace;
use opentelemetry::sdk::Resource;
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_semantic_conventions as semcov;
use tracing::Subscriber;
use tracing_opentelemetry::OtelData;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
// 初始化并注册全局的 tracing subscriber 和 OpenTelemetry pipeline
pub fn init_telemetry() {
// 设置服务名等资源属性
let resource = Resource::new(vec