feat(server): 健康检查增强 — 新增 /health/ready 就绪检查
- 保留 /health 轻量存活检查 - 新增 /health/ready 含 DB ping + Redis ping 并行检测 - 返回 status(ok/degraded/unavailable) + 各组件延迟和错误信息
This commit is contained in:
@@ -13,9 +13,7 @@ pub struct HealthResponse {
|
|||||||
pub modules: Vec<String>,
|
pub modules: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GET /health
|
/// GET /health — 轻量存活检查
|
||||||
///
|
|
||||||
/// 服务健康检查,返回运行状态和已注册模块列表
|
|
||||||
pub async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
|
pub async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
|
||||||
let modules = state
|
let modules = state
|
||||||
.module_registry
|
.module_registry
|
||||||
@@ -31,6 +29,106 @@ pub async fn health_check(State(state): State<AppState>) -> Json<HealthResponse>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn health_check_router() -> Router<AppState> {
|
#[derive(Debug, Serialize)]
|
||||||
Router::new().route("/health", get(health_check))
|
pub struct ReadyResponse {
|
||||||
|
pub status: String,
|
||||||
|
pub version: String,
|
||||||
|
pub database: ComponentStatus,
|
||||||
|
pub redis: ComponentStatus,
|
||||||
|
pub modules: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct ComponentStatus {
|
||||||
|
pub status: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub latency_ms: Option<u64>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /health/ready — 就绪检查(含 DB + Redis 连通性)
|
||||||
|
pub async fn readiness_check(State(state): State<AppState>) -> Json<ReadyResponse> {
|
||||||
|
let modules = state
|
||||||
|
.module_registry
|
||||||
|
.modules()
|
||||||
|
.iter()
|
||||||
|
.map(|m| m.name().to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (db_status, redis_status) = tokio::join!(
|
||||||
|
check_database(&state.db),
|
||||||
|
check_redis(&state.redis),
|
||||||
|
);
|
||||||
|
|
||||||
|
let overall = if db_status.status == "ok" && redis_status.status == "ok" {
|
||||||
|
"ok"
|
||||||
|
} else if db_status.status == "ok" {
|
||||||
|
"degraded"
|
||||||
|
} else {
|
||||||
|
"unavailable"
|
||||||
|
};
|
||||||
|
|
||||||
|
Json(ReadyResponse {
|
||||||
|
status: overall.to_string(),
|
||||||
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
database: db_status,
|
||||||
|
redis: redis_status,
|
||||||
|
modules,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_database(db: &sea_orm::DatabaseConnection) -> ComponentStatus {
|
||||||
|
use sea_orm::ConnectionTrait;
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
let stmt = sea_orm::Statement::from_string(
|
||||||
|
sea_orm::DatabaseBackend::Postgres,
|
||||||
|
"SELECT 1".to_string(),
|
||||||
|
);
|
||||||
|
match db.query_one(stmt).await {
|
||||||
|
Ok(_) => ComponentStatus {
|
||||||
|
status: "ok".to_string(),
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
error: None,
|
||||||
|
},
|
||||||
|
Err(e) => ComponentStatus {
|
||||||
|
status: "error".to_string(),
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
error: Some(e.to_string()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_redis(client: &redis::Client) -> ComponentStatus {
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
match client.get_multiplexed_async_connection().await {
|
||||||
|
Ok(mut conn) => {
|
||||||
|
match redis::cmd("PING")
|
||||||
|
.query_async::<String>(&mut conn)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => ComponentStatus {
|
||||||
|
status: "ok".to_string(),
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
error: None,
|
||||||
|
},
|
||||||
|
Err(e) => ComponentStatus {
|
||||||
|
status: "error".to_string(),
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
error: Some(e.to_string()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => ComponentStatus {
|
||||||
|
status: "error".to_string(),
|
||||||
|
latency_ms: Some(start.elapsed().as_millis() as u64),
|
||||||
|
error: Some(e.to_string()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn health_check_router() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/health", get(health_check))
|
||||||
|
.route("/health/ready", get(readiness_check))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user