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 продолжает получать трафик ещё несколько секунд — добавляйте preStop sleep в 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 и опирается на реальные контракты официальной документации.

Sources

Related topics