gRPC-GoMiddleCoding
Как реализовать graceful shutdown gRPC-сервера на Go?
Вызовите GracefulStop() после получения SIGTERM — метод дожидается завершения активных RPC. Добавьте таймаут с fallback на Stop() и переводите health-статус в NOT_SERVING до начала shutdown.
Graceful Shutdown gRPC-сервера на Go
Graceful shutdown означает: сервер перестаёт принимать новые соединения и запросы, дожидается завершения уже активных RPC-вызовов и только после этого освобождает ресурсы. В gRPC-Go это реализуется методом GracefulStop().
Базовая реализация
package main
import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"
"time"
"google.golang.org/grpc"
pb "example.com/myservice"
)
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
srv := grpc.NewServer()
pb.RegisterMyServiceServer(srv, &myServiceImpl{})
// Запуск в фоне
go func() {
if err := srv.Serve(lis); err != nil {
log.Printf("server stopped: %v", err)
}
}()
// Ожидание SIGINT / SIGTERM
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutdown signal received")
// GracefulStop ждёт завершения активных RPC
doneCh := make(chan struct{})
go func() {
srv.GracefulStop()
close(doneCh)
}()
// Таймаут на graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
select {
case <-doneCh:
log.Println("graceful shutdown complete")
case <-ctx.Done():
log.Println("shutdown timeout, forcing stop")
srv.Stop()
}
}
Разница между GracefulStop и Stop
GracefulStop()— закрывает listener, ждёт завершения всех активных RPC, затем закрывает соединения.Stop()— немедленно закрывает все соединения, прерывает активные RPC с кодомUnavailable.
Обработка streaming RPC при shutdown
Долгоживущие стримы могут заблокировать GracefulStop() на неопределённое время. Правильный подход — слушать закрытие контекста стрима внутри хендлера:
func (s *myServiceImpl) StreamData(
req *pb.Request,
stream pb.MyService_StreamDataServer,
) error {
for {
select {
case <-stream.Context().Done():
// Клиент отменил или сервер завершает работу
return stream.Context().Err()
case data := <-s.dataCh:
if err := stream.Send(data); err != nil {
return err
}
}
}
}
Интеграция с health checking во время shutdown
До вызова GracefulStop() следует перевести health-статус в NOT_SERVING, чтобы load balancer прекратил слать трафик:
import "google.golang.org/grpc/health"
import "google.golang.org/grpc/health/grpc_health_v1"
healthSrv := health.NewServer()
grpc_health_v1.RegisterHealthServer(srv, healthSrv)
healthSrv.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING)
// При shutdown:
healthSrv.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING)
time.Sleep(5 * time.Second) // ждём, пока LB увидит статус
srv.GracefulStop()
Подводные камни
GracefulStop()блокируется навсегда, если хотя бы один streaming RPC не завершается — обязательно нужен таймаут с fallback наStop().- Без перевода health-статуса в
NOT_SERVINGперед shutdown load balancer продолжает слать трафик ещё несколько секунд после начала остановки. signal.Notifyтребует буферизованного канала (размер >= 1), иначе сигнал может быть потерян.- Если хендлеры не проверяют
ctx.Done(), они не узнают о shutdown и продолжают работу. - Множественный вызов
GracefulStop()илиStop()не является thread-safe — вызывайте только один раз из одной горутины. - В Kubernetes
SIGTERMприходит, но pod продолжает получать трафик ещё несколько секунд — добавляйтеpreStopsleep в lifecycle hook. - Не закрытый вручную listener до
GracefulStop()не является проблемой —GracefulStop()закрывает его сам.
Common mistakes
- Давать ответ про graceful shutdown gRPC-сервера на Go только на уровне определения, не показывая поведение в реальном приложении.
- Игнорировать границы ответственности вокруг темы «graceful shutdown gRPC-сервера на Go»: кто отменяет работу, кто владеет ресурсом и где формируется ответ клиенту.
- Не связывать graceful shutdown gRPC-сервера на Go с observability, тестированием или безопасностью, когда это влияет на продакшен-поведение.
What the interviewer is testing
- Точно объясняет, что именно делает graceful shutdown gRPC-сервера на Go и где это используется в Go-коде.
- Связывает graceful shutdown gRPC-сервера на Go с корректным lifecycle запроса, отменой, конкурентностью или конфигурацией сервера там, где это уместно.
- Не изобретает API и опирается на реальные контракты официальной документации.