跳转到内容

健康检查与就绪检查

本指南展示如何使用 health 模块检查各个依赖,并通过 /healthz(存活检查)和 /readyz(就绪检查)端点暴露结果。

边界原理请参见 Health Primer

  • 存活检查与就绪检查的区别
  • 为数据库依赖实现 health.ComponentChecker
  • 构建 health.ReadinessStatus 聚合
  • 在路由注册中接入两个端点
端点回答的问题何时返回非 200
/healthz进程是否在运行并能提供 HTTP 服务?几乎从不 — 仅在进程应该被杀死时
/readyz服务是否准备好接收流量?启动期间、依赖故障,或受控排空时

就绪检查失败告诉编排器停止发送流量。存活检查失败告诉它重启进程。保持二者分离。

第一步 — 为每个依赖实现 ComponentChecker

Section titled “第一步 — 为每个依赖实现 ComponentChecker”
internal/health/dbcheck.go
package health
import (
"context"
"database/sql"
"fmt"
)
type DBChecker struct {
DB *sql.DB
}
func (c *DBChecker) Name() string { return "database" }
func (c *DBChecker) Check(ctx context.Context) error {
if err := c.DB.PingContext(ctx); err != nil {
return fmt.Errorf("database ping failed: %w", err)
}
return nil
}
internal/handler/health.go
package handler
import (
"context"
"net/http"
"time"
"github.com/spcent/plumego/contract"
"github.com/spcent/plumego/health"
)
type HealthHandler struct {
ServiceName string
Checkers []health.ComponentChecker
}
func (h *HealthHandler) Live(w http.ResponseWriter, r *http.Request) {
_ = contract.WriteResponse(w, r, http.StatusOK, health.HealthStatus{
Status: health.StatusHealthy,
Message: "ok",
Timestamp: time.Now(),
}, nil)
}
func (h *HealthHandler) Ready(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
components := make(map[string]bool, len(h.Checkers))
allReady := true
for _, checker := range h.Checkers {
err := checker.Check(ctx)
ready := err == nil
components[checker.Name()] = ready
if !ready {
allReady = false
}
}
status := http.StatusOK
if !allReady {
status = http.StatusServiceUnavailable
}
_ = contract.WriteResponse(w, r, status, health.ReadinessStatus{
Ready: allReady,
Timestamp: time.Now(),
Components: components,
}, nil)
}

第三步 — 在 RegisterRoutes 中注册 handler

Section titled “第三步 — 在 RegisterRoutes 中注册 handler”
func (a *App) RegisterRoutes() error {
h := &handler.HealthHandler{
ServiceName: "myapp",
Checkers: []health.ComponentChecker{
&apphealth.DBChecker{DB: a.DB},
},
}
if err := a.Core.Get("/healthz", http.HandlerFunc(h.Live)); err != nil {
return err
}
return a.Core.Get("/readyz", http.HandlerFunc(h.Ready))
}

存活 handler 几乎不应该失败。它的存在是为了确认进程可以提供 HTTP 服务,而不是验证依赖。数据库故障应该导致就绪检查失败,而不是存活检查失败:

func (h *HealthHandler) Live(w http.ResponseWriter, r *http.Request) {
// 这里没有依赖检查 — 只是确认进程存活。
_ = contract.WriteResponse(w, r, http.StatusOK,
health.HealthStatus{Status: health.StatusHealthy, Timestamp: time.Now()}, nil)
}
  • ComponentChecker 将每个依赖的健康逻辑隔离,各自可独立测试。
  • 就绪 handler 聚合多个 checker,在一个响应中报告每个组件的状态。
  • 5 秒 context 超时防止缓慢的依赖无限期阻塞就绪探测。
  • 存活检查和就绪检查作为独立路由注册 — 它们可以独立演进。