Go 实现程序优雅退出

在Go语言中,实现程序的优雅退出是一项重要的任务,特别是在涉及到HTTP服务器、gRPC服务器、以及其他后台工作的情况下。 

在实际应用中,通常建议同时监听 os.Interruptsyscall.SIGTERM,因为它们都是常见的终止信号,可以确保你的程序能够优雅地响应不同的关闭场景。例如,在生产环境中,系统管理员可能会使用 SIGTERM 来终止服务,而不是依赖于 Ctrl+C

HTTP Server 平滑关闭

Go 1.8及以上版本提供了 http.Server 结构的 Shutdown 方法,用于平滑关闭HTTP服务器。

案例一: 

package mainimport ("context""fmt""log""net/http""os""os/signal""time"
)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 信号,这是用户向程序发送中断信号(如Ctrl+C)时产生的信号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 ("context""fmt""log""net/http""os""os/signal""time"
)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 信号,这是用户向程序发送中断信号(如Ctrl+C)时产生的信号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 ("fmt""google.golang.org/grpc""google.golang.org/grpc/reflection""log""net""os""os/signal""syscall"
)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 是服务名称。以下是注册服务的一般步骤:

  1. 定义服务接口: 首先,你需要定义服务接口,这通常在 .proto 文件中完成。例如,如果你有一个名为 Greeter 的服务,它将包含一个 SayHello 方法。

  2. 生成服务代码: 使用 protoc 编译器和 gRPC 插件为 Go 生成服务代码。这将生成两个文件:<service_name>.pb.go<service_name>_grpc.pb.go。第一个文件包含消息类型的定义,第二个文件包含服务接口的定义。

  3. 创建服务实现: 创建一个结构体来实现服务接口。这个结构体需要实现 .proto 文件中定义的所有方法。

  4. 注册服务: 在你的主函数中,创建一个 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 user's name.
message HelloRequest {string name = 1;
}// The response message containing the greetings.
message HelloReply {string message = 1;
}

 生成 Go 代码:

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative your_service.proto

 创建服务实现:

package mainimport ("context""log""google.golang.org/grpc"pb "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 ("context""fmt""os""os/signal""sync""time"
)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 ("fmt""os""os/signal""sync"
)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 ("context""fmt""github.com/gin-gonic/gin""net/http""os""os/signal""syscall""time"
)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 ("context""log""net/http""os""os/signal""syscall""time""github.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() {// 监听中断信号,通常是Ctrl+C或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("服务器已退出")
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/840469.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spark在YARN上运行图解(资源调度+任务调度)及案例

前提&#xff1a;已经安装了spark集群&#xff0c;可参考上篇文章搭建&#xff1a;http://t.csdnimg.cn/UXBOp 一、Spark集群配置YARN 1、增加hadoop 配置文件地址 vim spark-env.sh 增加export HADOOP_CONF_DIR/usr/local/soft/hadoop-3.1.1/etc/hadoop 2、关闭虚拟内存 cd …

K8s基本概念初识

K8S基本概念 Container&#xff08;容器&#xff09; 容器是一种便携式、轻量级的操作系统级虚拟化技术。它使用命名空间&#xff08;namespaces&#xff09;来隔离不同的软件运行环境&#xff0c;并通过镜像&#xff08;images&#xff09;自包含软件的运行环境。容器技术使…

【图像超分】论文精读:Residual Non-local Attention Networks for Image Restoration(RNAN)

第一次来请先看这篇文章:【超分辨率(Super-Resolution)】关于【超分辨率重建】专栏的相关说明,包含专栏简介、专栏亮点、适配人群、相关说明、阅读顺序、超分理解、实现流程、研究方向、论文代码数据集汇总等) 文章目录 前言Abstract1 INTRODUCTION2 RELATED WORK3 RESIDU…

51单片机系统练习

头文件内容&#xff1a; /*-------------------------------------------------------------------------- AT89X52.HHeader file for the low voltage Flash Atmel AT89C52 and AT89LV52. Copyright (c) 1988-2002 Keil Elektronik GmbH and Keil Software, Inc. All rights …

JVM学习-虚拟机层面看String

String基本特性 String字符串&#xff0c;使用一对“”引起来表示 String s1 “lotus”; //字面量定义方式String s2 new String(“hello”) String声明为final,不可被继承String实现了Serializable接口&#xff0c;表示字符串支持序列化&#xff0c;实现Comparable接口&…

供应链金融模式学习资料

目录 产生背景 供应链金融的诞生 供应链金额的六大特征

结构安全预警?事前发现?人工观测VS自动化监测,谁更胜一筹?

人工检测是依靠目测检查或借助于便携式仪器测量得到的信息&#xff0c;但是随着整个行业的发展&#xff0c;传统的人工检测方法已经不能满足检测需求&#xff0c;从人工检测到自动化监测已是必然趋势。 a. 从检测方式看 人工检测需要耗费大量的精力&#xff0c;从摆放检测工具到…

Golang | Leetcode Golang题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; func isBalanced(root *TreeNode) bool {return height(root) > 0 }func height(root *TreeNode) int {if root nil {return 0}leftHeight : height(root.Left)rightHeight : height(root.Right)if leftHeight -1 || rightHeight -1 …

最热门好用骨传导耳机推荐!!分享六大实用选购技巧助你挑选!

耳机基本是每人人手一台&#xff0c;不管是在地铁上还是在公交上&#xff0c;都可以看到很多人戴着耳机度过空余的时光&#xff0c;甚至现在人们在耳机的选择方面更加偏向于骨传导耳机&#xff0c;开放耳道的奇特设计在户外佩戴的时候可以更好的感知到周围的环境音&#xff0c;…

java标准库介绍

Java 提供了一个丰富且功能强大的标准库,也称为 Java API(Application Programming Interface)。这些库涵盖了广泛的功能,从基础数据结构和集合,到并发编程、网络通信、图形界面和XML处理等。以下是一些最基本和常用的 Java 库和包: 1. java.lang 包 java.lang 包是 Ja…

java单元测试:JUnit测试运行器

JUnit测试运行器&#xff08;Test Runner&#xff09;决定了JUnit如何执行测试。JUnit有多个测试运行器&#xff0c;每个运行器都有特定的功能和用途。 1. 默认运行器 当没有显式指定运行器时&#xff0c;JUnit会使用默认运行器&#xff0c;这在JUnit 4和JUnit 5之间有所不同…

基于多模态MRI中深层语义和边缘信息融合的脑肿瘤分割 | 文献速递-深度学习肿瘤自动分割

Title 题目 Brain tumor segmentation based on the fusion of deep semantics and edge information in multimodal MRI 基于多模态MRI中深层语义和边缘信息融合的脑肿瘤分割 01 文献速递介绍 医学图像分割是医学图像处理领域的重要课题。其中&#xff0c;脑肿瘤分割旨在…

基础5 探索JAVA图形编程桌面:字符操作组件详解

在繁华都市的一个角落&#xff0c;卧龙和凤雏相聚在他们常去的台球厅。灯光洒在绿色的台球桌上&#xff0c;彩色的台球整齐地排列着&#xff0c;仿佛在等待着一场激烈的角逐。 卧龙轻轻地拿起球杆&#xff0c;微微瞄准&#xff0c;然后用力一击&#xff0c;白球带着一股强大的力…

C#_库的引用

类库的引用 还可以自己引用类库&#xff1a;解决方案-添加-新建项目 主程序 using System; using System.Windows.Forms; using Tools;namespace ConsoleApp2 {class Program{static void Main(string[] args){//Console.WriteLine("helloword");// Form form ne…

[力扣]——70.爬楼梯

题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 本题较为简单&#xff0c;主要用到递归思想 int fun(int n,int memo[]) {if(memo[n]!-1) //如果备忘录中已经有记录了…

MVCC相关

文章目录 前情要点基于什么引擎并发事务产生的问题不可重复读和幻读区别Next-Key Lock的示例解决并发事务采用的隔离级别当前读(Current Read)快照读(Snapshot Read)参考 MVCC定义表里面的隐藏字段由db_roll_ptr串成的版本链ReadView可见性算法mvcc的可见性算法为什么要以提交的…

Java封装

什么是封装&#xff1f; 封装是指在将对象的状态信息隐藏在对象内部&#xff0c;不允许外部程序直接访问对象内部的信息&#xff0c;而是通过该类所提供的方法来实现对内部信息的操作和访问 封装的作用 隐藏类的实现细节 让使用者只能通过事先预定的方法来访问数据&#xff0c…

WMI技术介绍以及使用WMI技术获取系统信息

WMI简介 Windows Management Instrumentation&#xff08;WMI&#xff09;是Microsoft Windows操作系统中一个强大的管理框架&#xff0c;它允许管理员以及开发者以标准化的方式访问和控制系统的各种硬件、操作系统组件、应用程序以及网络资源。WMI是基于Web-Based Enterprise…

React Suspense与Concurrent Mode:异步渲染的未来

React的Suspense和Concurrent Mode是React 16.8及更高版本引入的概念&#xff0c;旨在提升用户体验和性能&#xff0c;特别是在处理异步数据加载和动画时。它们是React的下一代渲染策略的一部分&#xff0c;目的是实现更流畅的交互和更高效的资源调度。 Suspense Suspense是一…