138 lines
3.7 KiB
TypeScript
138 lines
3.7 KiB
TypeScript
import { Controller, Get, HttpException, HttpStatus } from "@nestjs/common";
|
||
import { sql } from "drizzle-orm";
|
||
import { getDb } from "../../config/database.js";
|
||
import { esClient } from "../../config/elasticsearch.js";
|
||
import { getRedis } from "../redis/redis.client.js";
|
||
import { isKafkaHealthy } from "../kafka/kafka.client.js";
|
||
import { checkPushGateway } from "../push/push-gateway.client.js";
|
||
|
||
const SERVICE_NAME = "msg";
|
||
|
||
/**
|
||
* 健康检查端点。
|
||
*
|
||
* 仲裁依据 G2:/readyz 多依赖检查(DB/ES/Redis/Kafka/PushGateway)。
|
||
*
|
||
* - GET /healthz:liveness,仅返回进程存活,不检查依赖。
|
||
* - GET /readyz:readiness,检查 DB(关键)+ ES/Redis/Kafka/PushGateway(非关键)。
|
||
* DB 不可用返回 503;其他依赖降级标记但不影响 readyz 状态码。
|
||
*/
|
||
|
||
type CheckStatus = "ok" | "disabled" | "error";
|
||
|
||
interface DependencyCheck {
|
||
status: CheckStatus;
|
||
error?: string;
|
||
}
|
||
|
||
interface ReadyzResponse {
|
||
status: "ok" | "degraded";
|
||
service: string;
|
||
timestamp: string;
|
||
checks: {
|
||
database: DependencyCheck;
|
||
elasticsearch: DependencyCheck;
|
||
redis: DependencyCheck;
|
||
kafka: DependencyCheck;
|
||
pushGateway: DependencyCheck;
|
||
};
|
||
}
|
||
|
||
@Controller()
|
||
export class HealthController {
|
||
@Get("healthz")
|
||
liveness(): { status: string; service: string; timestamp: string } {
|
||
return {
|
||
status: "ok",
|
||
service: SERVICE_NAME,
|
||
timestamp: new Date().toISOString(),
|
||
};
|
||
}
|
||
|
||
@Get("readyz")
|
||
async readiness(): Promise<ReadyzResponse> {
|
||
const checks: ReadyzResponse["checks"] = {
|
||
database: { status: "ok" },
|
||
elasticsearch: { status: "disabled" },
|
||
redis: { status: "disabled" },
|
||
kafka: { status: "ok" },
|
||
pushGateway: { status: "disabled" },
|
||
};
|
||
|
||
// 1. DB(关键依赖)
|
||
try {
|
||
const db = getDb();
|
||
await db.execute(sql`SELECT 1`);
|
||
} catch (error) {
|
||
checks.database = {
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : "database unreachable",
|
||
};
|
||
}
|
||
|
||
// 2. Elasticsearch(可选依赖)
|
||
if (esClient) {
|
||
try {
|
||
await esClient.ping();
|
||
checks.elasticsearch = { status: "ok" };
|
||
} catch (error) {
|
||
checks.elasticsearch = {
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : "ES unreachable",
|
||
};
|
||
}
|
||
}
|
||
|
||
// 3. Redis(可选依赖)
|
||
const redis = getRedis();
|
||
if (redis) {
|
||
try {
|
||
const pong = await redis.ping();
|
||
checks.redis = { status: pong === "PONG" ? "ok" : "error" };
|
||
} catch (error) {
|
||
checks.redis = {
|
||
status: "error",
|
||
error: error instanceof Error ? error.message : "Redis unreachable",
|
||
};
|
||
}
|
||
}
|
||
|
||
// 4. Kafka(非关键依赖,降级模式)
|
||
checks.kafka = { status: isKafkaHealthy() ? "ok" : "error" };
|
||
|
||
// 5. PushGateway(软失败,可选依赖)
|
||
try {
|
||
const pushOk = await checkPushGateway();
|
||
checks.pushGateway = { status: pushOk ? "ok" : "error" };
|
||
} catch {
|
||
checks.pushGateway = {
|
||
status: "error",
|
||
error: "PushGateway unreachable",
|
||
};
|
||
}
|
||
|
||
// DB 不可用 -> 503;其他依赖降级 -> 200 + degraded 标记
|
||
const dbOk = checks.database.status === "ok";
|
||
const allOk = dbOk && Object.values(checks).every((c) => c.status === "ok");
|
||
|
||
if (!dbOk) {
|
||
throw new HttpException(
|
||
{
|
||
status: "unavailable",
|
||
service: SERVICE_NAME,
|
||
timestamp: new Date().toISOString(),
|
||
checks,
|
||
},
|
||
HttpStatus.SERVICE_UNAVAILABLE,
|
||
);
|
||
}
|
||
|
||
return {
|
||
status: allOk ? "ok" : "degraded",
|
||
service: SERVICE_NAME,
|
||
timestamp: new Date().toISOString(),
|
||
checks,
|
||
};
|
||
}
|
||
}
|