服务治理
问题
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