gRPC的正常关闭

首先

这篇文章是Go 3 Advent Calendar 2020的第21天。

使用Prometheus来获取gRPC的指标时,下面是一个Graceful Shutdown的示例。
我们想要实现的目标是,在出现错误或特定信号时,在任何一个服务器上安全地停止所有服务器。

请注意

我们的目标是在使用Go构建的gRPC服务器中获取指标。

概括起来。

这次我们将使用run来创建Graceful Shutdown。
还有一个很方便的包叫做errgroup。

大致的背景

在尝试获取gRPC的指标时,我认为可以按照go-grpc-prometheus的方式来创建两个服务器,一个用于gRPC的HTTP2服务器,另一个用于Prometheus的HTTP服务器。以上摘录自此处。

    // メトリクス用のサーバを別のgoroutineで起動
    go func() {
        if err := httpServer.ListenAndServe(); err != nil {
            log.Fatal("Unable to start a http server.")
        }
    }()
    // gRPC用のサーバを起動
    log.Fatal(grpcServer.Serve(lis))

只是在上述的例子中,有一个类似的备注:“由于没有实施Graceful Shutdown,请在生产环境中不要使用。”
虽然有许多关于在单个服务器上实现Graceful Shutdown的示例可以参考,但是我对如何在两个服务器上实现Graceful Shutdown的方法不太了解,为了不忘记在公司内听到的信息,我打算写一篇文章。

優雅關機的回顧

如果收到特定的信号,服务器将调用以下方法正常关闭:
– 对于net/http:
Server.Shutdown
– 对于grpc:
Server.GracefulStop

使用run来实现优雅的关机。

与errgroup类似,但通过Group.Add()将执行函数和中断函数添加到Group中,通过Group.Run()由协程执行执行函数,
当执行函数完成时调用所有中断函数。
我个人认为,由于可以单独管理执行和中断,这非常方便。
请详阅README以了解更多信息。
在本例中,我认为会有以下结果。

    var g run.Group
    {
        c := make(chan os.Signal, 1)
        signal.Notify(c, syscall.SIGTERM, os.Interrupt)
        cancel := make(chan struct{})
        g.Add(
            func() error {
                select {
                case <-c:
                    log.Println("Received signal, exiting gracefully...")
                case <-cancel:
                }
                return nil
            },
            func(err error) {
                close(cancel)
            },
        )
    }
    {
        g.Add(
            func() error {
                if err := httpServer.ListenAndServe(); err != http.ErrServerClosed {
                    log.Printf("Failed to serve http server: %v", err)
                    return err
                }
                return nil
            },
            func(err error) {
                ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
                defer cancel()
                if err := httpServer.Shutdown(ctx); err != nil {
                    log.Printf("Failed to shutdown http server: %v", err)
                }
            },
        )
    }
    {
        g.Add(
            func() error {
                listener, err := net.Listen("tcp", ":9093")
                if err != nil {
                    log.Printf("Failed to listen grpc server: %v", err)
                    return err
                }
                if err := server.Serve(listener); err != nil {
                    log.Printf("Failed to serve grpc server: %v", err)
                    return err
                }
                return nil
            },
            func(err error) {
                server.GracefulStop()
            },
        )
    }
    if err := g.Run(); err != nil {
        log.Fatalf("error: %v", err)
    }

以下是三个模块:接收 Signal、启动 HTTP 服务器和启动 gRPC 服务器。可以参考 Prometheus 中的 run 函数的使用方法。

使用errgroup实现优雅关闭

暂定待商议…

总结

希望通过使用errgroup和run来轻松实现多个服务器的优雅关闭。

bannerAds