Axum 健康检查与指标暴露:让服务可被监控

举报
yd_232564628 发表于 2026/10/25 10:58:58 2026/10/25
【摘要】 前几篇我们接入了数据库、认证和日志,服务已经能跑起来了。但还有一个问题:怎么知道服务是健康的?怎么知道它现在压力大不大?这篇文章给 todo_service 加上健康检查接口和基础指标,让它更容易被监控系统发现和管理。 为什么需要健康检查?部署到服务器或容器环境后,运维侧通常需要一个接口来判断服务状态。常见的健康检查有三种:liveness:服务是否还活着?如果挂了,就重启。readines...

前几篇我们接入了数据库、认证和日志,服务已经能跑起来了。但还有一个问题:怎么知道服务是健康的?怎么知道它现在压力大不大?

这篇文章给 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 后端走到这一步,已经具备了生产级服务的基本可观测性。健康检查让运维知道服务状态,指标让团队知道服务表现,日志让开发者能定位问题。这三者加在一起,服务就不再是黑盒了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。