2026-04-25 01:18:30 +08:00

85 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package healthcheck 提供 im-service 的 HTTP 和 gRPC 健康检查统一状态模型。
package healthcheck
import (
"context"
"time"
"google.golang.org/grpc/codes"
healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
)
// GRPCServer 实现标准 grpc.health.v1.Health并把 SERVING 映射到 ready。
type GRPCServer struct {
// UnimplementedHealthServer 保持 gRPC health proto 的向前兼容行为。
healthgrpc.UnimplementedHealthServer
// state 是健康状态来源gRPC health 不维护独立状态。
state *State
}
// NewGRPCServer 创建 gRPC health server。
func NewGRPCServer(state *State) *GRPCServer {
return &GRPCServer{state: state}
}
// Check 按标准 gRPC health protocol 返回服务 ready 状态。
func (s *GRPCServer) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) {
if !s.supports(req.GetService()) {
// 标准 health 协议要求未知 service 返回 NotFound。
return nil, status.Error(codes.NotFound, "unknown service")
}
return &healthpb.HealthCheckResponse{Status: s.status(ctx)}, nil
}
// Watch 周期性推送 ready 状态变化,支持依赖健康检查原生 watch 的服务发现系统。
func (s *GRPCServer) Watch(req *healthpb.HealthCheckRequest, stream healthgrpc.Health_WatchServer) error {
if !s.supports(req.GetService()) {
// 未知服务先发送 SERVICE_UNKNOWN再等待客户端结束 stream。
if err := stream.Send(&healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVICE_UNKNOWN}); err != nil {
return status.Error(codes.Canceled, "health watch stream ended")
}
<-stream.Context().Done()
return status.Error(codes.Canceled, "health watch stream ended")
}
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
var last healthpb.HealthCheckResponse_ServingStatus = -1
for {
current := s.status(stream.Context())
if current != last {
// 只有状态变化时推送,减少健康 watch 的无意义网络噪音。
if err := stream.Send(&healthpb.HealthCheckResponse{Status: current}); err != nil {
return status.Error(codes.Canceled, "health watch stream ended")
}
last = current
}
select {
case <-ticker.C:
case <-stream.Context().Done():
// 客户端断开 watch 时按 canceled 返回,符合标准 health watch 语义。
return status.Error(codes.Canceled, "health watch stream ended")
}
}
}
func (s *GRPCServer) supports(service string) bool {
// 空 service 表示检查整个进程,具名 service 只接受 im-service。
return service == "" || service == s.state.ServiceName()
}
func (s *GRPCServer) status(ctx context.Context) healthpb.HealthCheckResponse_ServingStatus {
if Healthy(s.state.Ready(ctx)) {
// gRPC health 的 SERVING 对应 HTTP ready 成功,而不是单纯进程 live。
return healthpb.HealthCheckResponse_SERVING
}
return healthpb.HealthCheckResponse_NOT_SERVING
}