跳到主要内容

服务治理

问题

Go 微服务的服务治理包括哪些方面?如何实现健康检查和优雅停机?

答案

服务治理全景

治理能力说明Go 实现
健康检查检测服务是否可用gRPC Health、HTTP /health
优雅停机处理完在途请求再退出signal.NotifyContext
超时控制防止请求无限等待context.WithTimeout
重试失败自动重试gRPC retry、自定义
幂等性重试不产生副作用唯一 ID + 去重
日志结构化日志zap、zerolog
监控指标采集Prometheus + Grafana

健康检查

// HTTP 健康检查
r.GET("/health", func(c *gin.Context) {
// 检查依赖组件
if err := db.Ping(); err != nil {
c.JSON(503, gin.H{"status": "unhealthy", "db": err.Error()})
return
}
c.JSON(200, gin.H{"status": "healthy"})
})

// gRPC Health Check(标准协议)
import "google.golang.org/grpc/health"
import healthpb "google.golang.org/grpc/health/grpc_health_v1"

healthServer := health.NewServer()
healthpb.RegisterHealthServer(grpcServer, healthServer)
healthServer.SetServingStatus("user-service", healthpb.HealthCheckResponse_SERVING)

优雅停机

func main() {
// 创建可取消的 Context
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: router}

// 启动服务器
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()

log.Println("服务已启动")

// 等待关闭信号
<-ctx.Done()
log.Println("收到关闭信号,开始优雅停机...")

// 给在途请求 10 秒时间完成
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// 1. 从注册中心注销
deregister()
// 2. 停止接收新请求,等待在途请求完成
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("强制关闭: %v", err)
}
// 3. 关闭数据库连接
db.Close()

log.Println("服务已停止")
}

超时控制

// gRPC 客户端超时
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, &pb.Request{Id: 1})

// HTTP 客户端超时
httpClient := &http.Client{Timeout: 5 * time.Second}

// 中间件超时
func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer cancel()
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}

Prometheus 监控

import "github.com/prometheus/client_golang/prometheus"

// 定义指标
var (
httpRequestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total HTTP requests",
},
[]string{"method", "path", "status"},
)
httpRequestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
)

// 中间件采集
func MetricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start).Seconds()

httpRequestsTotal.WithLabelValues(
c.Request.Method, c.FullPath(), strconv.Itoa(c.Writer.Status()),
).Inc()
httpRequestDuration.WithLabelValues(
c.Request.Method, c.FullPath(),
).Observe(duration)
}
}

常见面试问题

Q1: 优雅停机的流程是什么?

答案

收到 SIGTERM
→ 1. 从注册中心注销(不再接收新流量)
→ 2. 等待在途请求完成(设超时,如 10s)
→ 3. 关闭数据库/Redis 连接
→ 4. 进程退出

K8s 发送 SIGTERM 后默认等 30s(terminationGracePeriodSeconds),超时发 SIGKILL 强杀。

Q2: 微服务之间调用超时怎么设置?

答案

  • 从入口向内递减:网关 10s → 服务 A 5s → 服务 B 3s
  • 用 Context 传播:上游 Context 剩余时间 < 下游超时则用上游的
  • 区分读写:读操作 3s,写操作 5s

相关链接