// Package healthcheck 提供 im-service 的 HTTP 和 gRPC 健康检查统一状态模型。 package healthcheck import ( "encoding/json" "net/http" "time" ) // Response 是 HTTP health endpoint 的稳定 JSON 契约。 type Response struct { // Status 是整体状态,只有全部检查成功才是 ok。 Status string `json:"status"` // Service 是服务名,便于多服务健康检查统一解析。 Service string `json:"service"` // NodeID 是当前实例标识,用于定位具体节点。 NodeID string `json:"node_id"` // TimeUnixMS 是响应生成时间,便于判断探针结果新鲜度。 TimeUnixMS int64 `json:"time_unix_ms"` // Checks 只在失败时返回,成功响应保持短小。 Checks []Check `json:"checks,omitempty"` } // HTTPHandler 暴露 /healthz/live 和 /healthz/ready 的只读探测入口。 type HTTPHandler struct { // state 是健康检查唯一数据源,handler 不缓存探测结果。 state *State } // NewHTTPHandler 创建 health HTTP handler。 func NewHTTPHandler(state *State) *HTTPHandler { return &HTTPHandler{state: state} } // Live 返回进程自身是否仍可运行;下游抖动不能影响 live。 func (h *HTTPHandler) Live(writer http.ResponseWriter, request *http.Request) { checks := h.state.Live() statusCode := http.StatusOK if !Healthy(checks) { // live 失败代表进程自身不可靠,返回 500 让 supervisor 可以重启。 statusCode = http.StatusInternalServerError } h.write(writer, statusCode, checks) } // Ready 返回当前实例是否可以接新流量。 func (h *HTTPHandler) Ready(writer http.ResponseWriter, request *http.Request) { checks := h.state.Ready(request.Context()) statusCode := http.StatusOK if !Healthy(checks) { // ready 失败只表示不接新流量,不一定需要重启进程。 statusCode = http.StatusServiceUnavailable } h.write(writer, statusCode, checks) } func (h *HTTPHandler) write(writer http.ResponseWriter, statusCode int, checks []Check) { status := componentStatus if !Healthy(checks) { // 整体状态和 HTTP status code 使用同一个 Healthy 聚合逻辑,避免语义分叉。 status = failStatus } response := Response{ Status: status, Service: h.state.ServiceName(), NodeID: h.state.NodeID(), TimeUnixMS: time.Now().UnixMilli(), } if status != componentStatus { // 只有失败时返回 checks,成功路径减少响应体和敏感运行信息暴露。 response.Checks = checks } // health endpoint 固定 JSON 输出,部署系统和本地调试都消费同一契约。 writer.Header().Set("Content-Type", "application/json") writer.WriteHeader(statusCode) _ = json.NewEncoder(writer).Encode(response) }