沈阳网站推广优化公司哪家好,中国上市网络公司排名,阜宁网站建设,莱芜新闻视频回放今天在Go语言中#xff0c;实现程序的优雅退出是一项重要的任务#xff0c;特别是在涉及到HTTP服务器、gRPC服务器、以及其他后台工作的情况下。
在实际应用中#xff0c;通常建议同时监听 os.Interrupt 和 syscall.SIGTERM#xff0c;因为它们都是常见的终止信号#xff0c…在Go语言中实现程序的优雅退出是一项重要的任务特别是在涉及到HTTP服务器、gRPC服务器、以及其他后台工作的情况下。
在实际应用中通常建议同时监听 os.Interrupt 和 syscall.SIGTERM因为它们都是常见的终止信号可以确保你的程序能够优雅地响应不同的关闭场景。例如在生产环境中系统管理员可能会使用 SIGTERM 来终止服务而不是依赖于 CtrlC
HTTP Server 平滑关闭
Go 1.8及以上版本提供了 http.Server 结构的 Shutdown 方法用于平滑关闭HTTP服务器。
案例一
package mainimport (contextfmtlognet/httposos/signaltime
)func main() {// 创建一个新的 ServeMux 对象它是HTTP请求多路复用器用于将不同的请求路由到不同的处理函数mux : http.NewServeMux()mux.HandleFunc(/, func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(Hello, World!))})server : http.Server{Addr: :8088, //监听端口Handler: mux, //监听的处理器}//监听并服务HTTP请求可以想办法开一个8088端口来占用比如在java起一个服务go func() {if err : server.ListenAndServe(); err ! nil {if err ! http.ErrServerClosed {// 处理监听失败的错误// 记录错误log.Printf(HTTP服务器失败: %v, err)// 执行清理工作,如有必要// 可选尝试重启服务器time.Sleep(10 * time.Second) //等待10秒再重启if !attemptRestart(server) {//os.Exit(1) 将导致程序立即退出并返回状态码 1 表示发生了错误。在实际应用中你可能需要根据错误的性质和程序的设计来决定是否退出程序或者采取其他的错误恢复策略// 优雅地退出程序os.Exit(1)}}}}()// 等待中断信号来优雅地关闭服务器stop : make(chan os.Signal, 1)// 用 signal.Notify 来监听 os.Interrupt 信号这是用户向程序发送中断信号如CtrlC时产生的信号signal.Notify(stop, os.Interrupt)-stop // 程序在此处阻塞直到接收到一个中断信号//当有中断信号来创建一个带有超时的 context.Context 对象超时时间为5秒ctx, cancel : context.WithTimeout(context.Background(), 5*time.Second)// 确保在函数返回时取消这个上下文释放相关资源defer cancel()//当接收到中断信号时调用 server.Shutdown 方法并传入上面创建的 ctx 对象以优雅地关闭服务器if err : server.Shutdown(ctx); err ! nil {// 如果在关闭过程中出现错误fmt.Println(处理关闭服务器时的错误)}
}// attemptRestart 尝试重启服务器
func attemptRestart(server *http.Server) bool {// 这里可以添加任何需要的清理或重启前的准备工作log.Println(正在尝试重新启动服务器。。。)// 尝试重新启动服务器err : server.ListenAndServe()if err ! nil err ! http.ErrServerClosed {log.Printf(无法重新启动服务器: %v, err)return false}log.Println(重启成功。。。)return true
}案例二持续监听
package mainimport (contextfmtlognet/httposos/signaltime
)func main() {// 创建一个新的 ServeMux 对象它是HTTP请求多路复用器用于将不同的请求路由到不同的处理函数mux : http.NewServeMux()mux.HandleFunc(/, func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(Hello, World!))})server : http.Server{Addr: :8088, //监听端口Handler: mux, //监听的处理器}go func() {// 等待中断信号来优雅地关闭服务器stop : make(chan os.Signal, 1)// 用 signal.Notify 来监听 os.Interrupt 信号这是用户向程序发送中断信号如CtrlC时产生的信号signal.Notify(stop, os.Interrupt)-stop // 程序在此处阻塞直到接收到一个中断信号//当有中断信号来创建一个带有超时的 context.Context 对象超时时间为5秒ctx, cancel : context.WithTimeout(context.Background(), 5*time.Second)// 确保在函数返回时取消这个上下文释放相关资源defer cancel()//当接收到中断信号时调用 server.Shutdown 方法并传入上面创建的 ctx 对象以优雅地关闭服务器if err : server.Shutdown(ctx); err ! nil {// 如果在关闭过程中出现错误fmt.Println(处理关闭服务器时的错误)}}()//监听并服务HTTP请求可以想办法开一个8088端口来占用比如在java起一个服务for {if err : server.ListenAndServe(); err ! nil {if err ! http.ErrServerClosed {// 处理监听失败的错误// 记录错误log.Printf(HTTP服务器失败: %v, err)// 执行清理工作,如有必要// 可选尝试重启服务器time.Sleep(5 * time.Second) //等待10秒再重启attemptRestart(server)//os.Exit(1) 将导致程序立即退出并返回状态码 1 表示发生了错误。在实际应用中你可能需要根据错误的性质和程序的设计来决定是否退出程序或者采取其他的错误恢复策略// 优雅地退出程序//os.Exit(1)}}}
}// attemptRestart 尝试重启服务器
func attemptRestart(server *http.Server) bool {// 这里可以添加任何需要的清理或重启前的准备工作log.Println(正在尝试重新启动服务器。。。)// 尝试重新启动服务器err : server.ListenAndServe()if err ! nil err ! http.ErrServerClosed {log.Printf(无法重新启动服务器: %v, err)return false}return true
}
gRPC Server 平滑关闭
gRPC服务器的平滑关闭可以通过 GracefulStop 方法实现
package mainimport (fmtgoogle.golang.org/grpcgoogle.golang.org/grpc/reflectionlognetosos/signalsyscall
)func main() {lis, err : net.Listen(tcp, :50051)if err ! nil {log.Fatalf(监听失败: %v, err)}s : grpc.NewServer()// 注册服务...需要自己写// 在gRPC服务上启用反射服务//启用反射服务后客户端可以使用 gRPC 反射 API 查询服务器支持的服务列表、服务下的方法列表等信息。//这对于开发和测试阶段非常有用因为它允许客户端在没有预先定义 .proto 文件的情况下与服务器通信。reflection.Register(s)// 监听系统关闭信号sigs : make(chan os.Signal, 1)signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)go func() {-sigsfmt.Println(收到停止信号正在正常停止gRPC服务器。。。)s.GracefulStop() // 调用 GracefulStop 方法来平滑关闭服务器}()//如果 s.Serve(lis) 调用成功服务器将正常运行等待和处理客户端的请求。//如果发生错误比如监听出现问题或者服务器无法处理请求err 变量将包含相应的错误信息if err : s.Serve(lis); err ! nil {log.Fatalf(服务失败: %v, err)}//go func() {// if err : s.Serve(lis); err ! nil {// // 处理gRPC服务启动错误// }//}()
}
在 gRPC 中注册服务是指将服务的实现与 gRPC 服务器关联起来。在 Go 语言中这通常通过调用服务接口的 RegisterXXXServer 方法来完成其中 XXX 是服务名称。以下是注册服务的一般步骤 定义服务接口 首先你需要定义服务接口这通常在 .proto 文件中完成。例如如果你有一个名为 Greeter 的服务它将包含一个 SayHello 方法。 生成服务代码 使用 protoc 编译器和 gRPC 插件为 Go 生成服务代码。这将生成两个文件service_name.pb.go 和 service_name_grpc.pb.go。第一个文件包含消息类型的定义第二个文件包含服务接口的定义。 创建服务实现 创建一个结构体来实现服务接口。这个结构体需要实现 .proto 文件中定义的所有方法。 注册服务 在你的主函数中创建一个 gRPC 服务器实例并使用生成的服务注册函数将服务实现注册到服务器上。
下面是一个简单的示例演示了如何注册一个名为 Greeter 的服务
假设你的 .proto 文件定义如下
syntax proto3;package example;// The greeter service definition.
service Greeter {// Sends a greetingrpc SayHello (HelloRequest) returns (HelloReply) {}
}// The request message containing the users name.
message HelloRequest {string name 1;
}// The response message containing the greetings.
message HelloReply {string message 1;
} 生成 Go 代码
protoc --go_out. --go_optpathssource_relative --go-grpc_out. --go-grpc_optpathssource_relative your_service.proto 创建服务实现
package mainimport (contextloggoogle.golang.org/grpcpb path/to/your_package // 替换为你的包路径
)// server 是 GreeterServer 的实现。
type server struct {pb.UnimplementedGreeterServer
}// SayHello 实现 GreeterServer 的 SayHello 方法。
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {log.Printf(Received: %v, in.GetName())return pb.HelloReply{Message: Hello in.GetName()}, nil
} 在主函数中注册服务
func main() {lis, err : net.Listen(tcp, :50051)if err ! nil {log.Fatalf(failed to listen: %v, err)}defer lis.Close()s : grpc.NewServer()pb.RegisterGreeterServer(s, server{}) // 注册服务if err : s.Serve(lis); err ! nil {log.Fatalf(failed to serve: %v, err)}
}
在这个示例中pb.RegisterGreeterServer 是由 protoc 生成的函数用于将 server 实例注册到 gRPC 服务器上。pb 是你的包名它是由 protoc 编译器根据 .proto 文件的包声明生成的。
请确保将 path/to/your_package 替换为实际的包路径这个路径指向包含你的 .proto 文件生成的 Go 代码的位置。
Worker 协程平滑关闭
对于worker协程的平滑关闭可以使用 context.Context 实现
package mainimport (contextfmtosos/signalsynctime
)func worker(ctx context.Context, wg *sync.WaitGroup) {defer wg.Done()for {select {case -ctx.Done():fmt.Println(worker收到停机信号)returndefault:// 执行工作任务fmt.Println(Working...)time.Sleep(time.Second)}}
}func main() {ctx, cancel : context.WithCancel(context.Background())var wg sync.WaitGroup// 启动worker协程wg.Add(1)go worker(ctx, wg)// 等待中断信号来优雅地关闭worker协程stop : make(chan os.Signal, 1)signal.Notify(stop, os.Interrupt)-stop // 等待中断信号fmt.Println(正在关闭。。。)// 发送关闭信号给worker协程cancel()wg.Wait()fmt.Println(关闭完成)
}实现 io.Closer 接口的自定义服务平滑关闭
实现 io.Closer 接口的服务可以通过调用 Close 方法进行平滑关闭 package mainimport (fmtosos/signalsync
)type MyService struct {mu sync.Mutex// 其他服务相关的字段
}// MyService 实现了 Close 方法那么它就隐式地实现了 io.Closer 接口
func (s *MyService) Close() error {s.mu.Lock()defer s.mu.Unlock()// 执行关闭服务的操作fmt.Println(正在关闭MyService。。。)return nil
}func main() {service : MyService{}// 等待中断信号来优雅地关闭服务stop : make(chan os.Signal, 1)signal.Notify(stop, os.Interrupt)-stop // 等待中断信号fmt.Println(正在关闭。。。)// 调用Close方法进行平滑关闭if err : service.Close(); err ! nil {fmt.Println(关闭服务时出错:, err)}fmt.Println(关闭完成)
}
以上是一些Golang中实现程序优雅退出的方法具体的实现方式取决于你的应用程序结构和使用的库。在实际应用中你可能需要组合使用这些方法以确保整个应用程序在退出时都能够平滑关闭。
在实际项目中的应用(Gin)
平滑关闭会阻止新的请求进来并等待目前正在进行的业务处理完成(此处取决于timeout设置的时间如果设置的时间过短请求未完成就会服务器被迫关闭:context deadline exceeded)
package mainimport (contextfmtgithub.com/gin-gonic/ginnet/httposos/signalsyscalltime
)func main() {r : gin.Default()// 定义路由r.GET(/ping, func(c *gin.Context) {time.Sleep(5 * time.Second) //模拟业务处理时间c.String(http.StatusOK, pong)})// 创建 HTTP 服务器srv : http.Server{Addr: :8080, Handler: r}// 等待中断信号来优雅地关闭服务stop : make(chan os.Signal, 1)signal.Notify(stop, os.Interrupt, syscall.SIGTERM)// 在新的 Goroutine 中启动 HTTP 服务器go func() {if err : srv.ListenAndServe(); err ! nil err ! http.ErrServerClosed {fmt.Printf(listen: %v\n, err)os.Exit(1)}}()// 阻塞直到接收到停止信号-stop// 创建一个 10 秒的超时上下文ctx, cancel : context.WithTimeout(context.Background(), 10*time.Second)defer cancel()// 优雅关闭 HTTP 服务器if err : srv.Shutdown(ctx); err ! nil {fmt.Printf(无法正常关闭服务器: %v\n, err)}fmt.Println(服务器正常关闭)
}
package mainimport (contextlognet/httposos/signalsyscalltimegithub.com/gin-gonic/gin
)func main() {r : gin.Default()// 添加路由等设置...r.GET(/ping, func(c *gin.Context) {time.Sleep(5 * time.Second)c.JSON(http.StatusOK, gin.H{message: pong,})})// 启动HTTP服务器srv : http.Server{Addr: :8080,Handler: r,}// 创建一个用于通知服务器关闭的channeldone : make(chan bool)go func() {// 监听中断信号通常是CtrlC或Kill命令sig : make(chan os.Signal, 1)signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)-sig // 等待信号// 收到信号后给出日志提示log.Println(Shutdown Server ...)// 调用Server的Shutdown方法传入一个有超时上下文ctx, cancel : context.WithTimeout(context.Background(), 10*time.Second)defer cancel()if err : srv.Shutdown(ctx); err ! nil {log.Fatal(服务器被迫关闭:, err)}close(done)}()// 启动HTTP服务log.Println(正在端口8080上启动服务器。。。)if err : srv.ListenAndServe(); err ! http.ErrServerClosed {log.Fatalf(listen: %s\n, err)}-done // 等待直到shutdown完成log.Println(服务器已退出)
}