微服务优雅下线的核心挑战
微服务滚动更新时,旧实例收到SIGTERM信号后立即退出会导致正在处理的请求被中断,返回5xx错误。优雅下线的目标是确保存量请求处理完成、连接池排空、注册中心摘除后才退出进程。在gRPC微服务场景中,由于长连接和多路复用的特性,流量排空比HTTP/1.1更复杂,需要配合健康检查协议精确控制流量迁移节奏。
gRPC健康检查协议集成
gRPC标准健康检查协议(grpc.health.v1)让负载均衡器实时感知服务端状态。服务端在收到终止信号时先将自己的健康状态置为NOT_SERVING,负载均衡器感知后停止向该实例转发新请求,存量请求继续处理:
package main
import (
"context"
"net"
"os"
"os/signal"
"syscall"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
pb "your-project/proto"
)
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("监听失败: %v", err)
}
server := grpc.NewServer()
pb.RegisterOrderServiceServer(server, &orderService{})
// 注册健康检查服务
healthServer := health.NewServer()
grpc_health_v1.RegisterHealthServer(server, healthServer)
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
go server.Serve(lis)
// 信号监听
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
log.Println("收到终止信号,开始优雅下线...")
// 第一步:标记为NOT_SERVING
healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING)
log.Println("健康状态已置为NOT_SERVING")
// 第二步:等待流量排空
time.Sleep(15 * time.Second)
// 第三步:优雅停止
server.GracefulStop()
log.Println("服务已优雅停止")
}
流量排空时间计算策略
排空等待时间并非固定值,需根据实际请求处理时长动态计算。推荐取P99延迟的2-3倍作为基准,再叠加注册中心心跳间隔的缓冲。通过Prometheus采集的grpc_server_handling_seconds指标可精确获取P99延迟:
// 动态计算排空时间
func calculateDrainTimeout() time.Duration {
// 从环境变量读取,默认30秒
timeout := getEnvInt("DRAIN_TIMEOUT_SECONDS", 30)
// 读取最近5分钟的P99延迟
p99 := queryPrometheus(
"histogram_quantile(0.99, rate(grpc_server_handling_seconds_bucket[5m]))",
)
// 排空时间 = max(P99 * 3, 最小值)
drainSeconds := max(int(p99*3), timeout)
return time.Duration(drainSeconds) * time.Second
}
Kubernetes Pod生命周期钩子配合
K8s环境下优雅下线需要preStop钩子与terminationGracePeriodSeconds协同。preStop钩子在SIGTERM之前执行,用于从注册中心摘除并等待排空:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: order-service
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
ports:
- containerPort: 50051
preStop中的sleep 15秒给kube-proxy更新iptables规则留出时间,确保流量不再路由到即将终止的Pod。配合健康检查的NOT_SERVING状态,实现双层流量排空保护。
连接级优雅关闭实现
gRPC使用HTTP/2长连接,单一连接上可能复用多个并发流。优雅关闭需等待活跃流处理完成而非简单关闭连接。grpc.Server.GracefulStop()内部实现了这一逻辑,但需要配合自定义拦截器监控活跃请求计数:
type activeRequests struct {
sync.WaitGroup
}
func (a *activeRequests) StreamInterceptor(
srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
a.Add(1)
defer a.Done()
return handler(srv, ss)
}
func (a *activeRequests) UnaryInterceptor(
ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
a.Add(1)
defer a.Done()
return handler(ctx, req)
}
下线流程中,在设置NOT_SERVING后调用activeRequests.Wait()等待所有活跃请求完成,再执行GracefulStop(),实现请求零丢失的优雅下线。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-yu-yan-wei-fu-wu-you-ya-xia-xian-grpc-jian-kang-jian-cha/