Axum 健康检查与指标暴露:让服务可被监控
前几篇我们接入了数据库、认证和日志,服务已经能跑起来了。但还有一个问题:怎么知道服务是健康的?怎么知道它现在压力大不大?
这篇文章给 todo_service 加上健康检查接口和基础指标,让它更容易被监控系统发现和管理。
为什么需要健康检查?
部署到服务器或容器环境后,运维侧通常需要一个接口来判断服务状态。常见的健康检查有三种:
- liveness:服务是否还活着?如果挂了,就重启。
- readiness:服务是否就绪?如果数据库还没连上,先别接流量。
- startup:启动阶段检查,避免启动过程中被误判为异常。
Rust 后端做健康检查很简单,但不要只返回一个 200 OK。有意义的健康检查应该能反映数据库、外部依赖等关键组件的状态。
第一步:基础健康检查接口
先写一个最简单的:
use axum::{http::StatusCode, Json};
use serde::Serialize;
#[derive(Serialize)]
struct HealthResponse {
status: String,
}
async fn health() -> (StatusCode, Json<HealthResponse>) {
(
StatusCode::OK,
Json(HealthResponse {
status: "ok".into(),
}),
)
}
挂载路由:
.route("/health", get(health))
这个接口只能说明 HTTP 服务在跑,但无法反映数据库是否正常。
第二步:深度健康检查
更实用的做法是把数据库连接池状态也检查进去:
use axum::{extract::State, http::StatusCode, Json};
#[derive(Serialize)]
struct DeepHealthResponse {
status: String,
database: String,
}
async fn deep_health(
State(state): State<AppState>,
) -> (StatusCode, Json<DeepHealthResponse>) {
match sqlx::query("SELECT 1").execute(&*state.db).await {
Ok(_) => (
StatusCode::OK,
Json(DeepHealthResponse {
status: "ok".into(),
database: "connected".into(),
}),
),
Err(_) => (
StatusCode::SERVICE_UNAVAILABLE,
Json(DeepHealthResponse {
status: "degraded".into(),
database: "disconnected".into(),
}),
),
}
}
这样 /health/deep 就能同时反映 HTTP 服务和数据库的状态。容器编排或负载均衡器可以根据返回码决定是否继续转发流量。
第三步:暴露基础指标
健康检查回答的是"服务是否正常",指标回答的是"服务现在怎么样"。
最基础的指标包括:
- 请求总数
- 请求耗时
- 错误数
- 活跃连接数
tower-http 已经提供了请求计数和耗时统计,我们可以结合 metrics 生态来做更完整的指标暴露。
先加依赖:
cargo add metrics metrics-exporter-prometheus
cargo add tower-http --features trace
初始化 Prometheus 导出器:
use metrics_exporter_prometheus::PrometheusBuilder;
fn init_metrics() {
PrometheusBuilder::new()
.install()
.expect("failed to install Prometheus exporter");
}
在 main 里调用:
#[tokio::main]
async fn main() {
init_tracing();
init_metrics();
// ...
}
然后暴露 /metrics 接口:
use axum::body::Body;
use metrics_exporter_prometheus::PrometheusHandle;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: Arc<PgPool>,
metrics_handle: PrometheusHandle,
}
async fn metrics(State(state): State<AppState>) -> Body {
Body::from(state.metrics_handle.render())
}
挂载路由:
.route("/metrics", get(metrics))
访问 /metrics 就能看到 Prometheus 格式的指标数据,包括请求数、延迟分布等。
第四步:自定义业务指标
除了自动收集的 HTTP 指标,还可以手动埋点业务指标。比如统计登录成功/失败次数:
use metrics::{counter, histogram};
async fn login(
State(state): State<AppState>,
Json(payload): Json<LoginRequest>,
) -> Result<Json<LoginResponse>, AppError> {
let start = std::time::Instant::now();
// ... 登录逻辑 ...
let elapsed = start.elapsed().as_secs_f64();
histogram!("login_duration_seconds", elapsed);
if login_success {
counter!("login_total", "status" => "success").increment(1);
} else {
counter!("login_total", "status" => "failure").increment(1);
}
// ...
}
这样在 Prometheus 里就能看到登录成功率、登录耗时分布等指标,方便做告警。
第五步:区分 liveness 和 readiness
生产环境通常建议分开两个接口:
.route("/health/live", get(liveness))
.route("/health/ready", get(readiness))
liveness 只检查进程是否存活:
async fn liveness() -> StatusCode {
StatusCode::OK
}
readiness 检查关键依赖是否就绪:
async fn readiness(State(state): State<AppState>) -> StatusCode {
match sqlx::query("SELECT 1").execute(&*state.db).await {
Ok(_) => StatusCode::OK,
Err(_) => StatusCode::SERVICE_UNAVAILABLE,
}
}
Kubernetes 里可以这样配置:
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
这样服务启动时不会立即接流量,数据库断开时也会被自动摘除。
第六步:限制指标接口访问
/metrics 暴露了服务内部状态,不建议公开访问。可以用中间件限制来源:
use axum::middleware::{self, Next};
use axum::http::{Request, StatusCode};
async fn restrict_metrics<B>(
req: Request<B>,
next: Next<B>,
) -> Result<axum::response::Response, StatusCode> {
let client_ip = req
.extensions()
.get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
.map(|addr| addr.0.ip());
// 只允许本地访问
if let Some(ip) = client_ip {
if ip.is_loopback() || ip.is_private() {
return Ok(next.run(req).await);
}
}
Err(StatusCode::FORBIDDEN)
}
挂载到 /metrics 路由:
.route("/metrics", get(metrics).layer(middleware::from_fn(restrict_metrics)))
生产环境更推荐用独立的 metrics 端口,或者通过反向代理做访问控制。
上线前的检查清单
服务部署前,建议确认这几项:
/health/live返回 200/health/ready能正确反映数据库状态/metrics能被 Prometheus 抓取- 关键业务路径有日志和指标
- 错误日志包含足够上下文
- 敏感接口有访问限制
- 日志有滚动策略,不会占满磁盘
Rust 后端走到这一步,已经具备了生产级服务的基本可观测性。健康检查让运维知道服务状态,指标让团队知道服务表现,日志让开发者能定位问题。这三者加在一起,服务就不再是黑盒了。
- 点赞
- 收藏
- 关注作者
评论(0)