1. 背景

  项目开发过程中,随着需求的迭代,代码的发布会频繁进行,在发布过程中,如何让程序做到优雅的退出?

为什么需要优雅的退出?

  • 你的 http 服务,监听端口没有关闭,客户的请求发过来了,但处理了一半,可能造成脏数据。
  • 你的协程 worker 的一个任务运行了一半,程序退出了,结果不符合预期。

如下我们以 http 服务,gRPC 服务,单独的 woker 协程为例子,一步步说明平滑关闭的写法。

2. 常见的几种平滑关闭

为了解决退出可能出现的潜在问题,平滑关闭一般做如下一些事情

  • 关闭对外的监听端口,拒绝新的连接
  • 关闭异步运行的协程
  • 关闭依赖的资源
  • 等待如上资源关闭
  • 然后平滑关闭

2.1 http server 平滑关闭

原来的写法

// startHttpServer start http server
func startHttpServer() {
mux := http.NewServeMux()
// mux.Handle("/metrics", promhttp.Handler())
if err := http.ListenAndServe(":1608", mux); err != nil {
log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
}
}

  

带平滑关闭的写法

// startHttpServer start http server
func startHttpServer() {
mux := http.NewServeMux()
// mux.Handle("/metrics", promhttp.Handler())
srv := &http.Server{
Addr: ":1608",
Handler: mux,
}
// 注册平滑关闭,退出时会调用 srv.Shutdown(ctx)
quit.GetQuitEvent().RegisterQuitCloser(srv)
if err := srv.ListenAndServe(); err != nil {
log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
}
}

把平滑关闭注册到http.Server的关闭函数中

// startHttpServer start http server
func startHttpServer() {
mux := http.NewServeMux()
// mux.Handle("/metrics", promhttp.Handler())
srv := &http.Server{
Addr: ":1608",
Handler: mux,
}
// 把平滑退出注册到http.Server中
srv.RegisterOnShutdown(quit.GetQuitEvent().GracefulStop)
if err := srv.ListenAndServe(); err != nil {
log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
}
}

  

2.2 gRPC server 平滑关闭

原来的写法

// startGrpcServer start grpc server
func startGrpcServer() {
listen, err := net.Listen("tcp", "0.0.0.0:9999")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
return
}
grpcServer := grpc.NewServer()
// helloBoot.GrpcRegister(grpcServer)
go grpcServer.Serve(listen)
defer grpcServer.GracefulStop()
// ...
}

带平滑关闭的写法 

// startGrpcServer start grpc server
func startGrpcServer() {
listen, err := net.Listen("tcp", "0.0.0.0:9999")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
return
}
grpcServer := grpc.NewServer()
// helloBoot.GrpcRegister(grpcServer)
go grpcServer.Serve(listen)
// 把 grpc 的GracefulStop注册到退出事件中
quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop)
quit.WaitSignal()
}

  

2.3 worker 协程平滑关闭

单独的协程启停,可以通过计数的方式注册到退出事件处理器中。

  • 启动协程 增加计数

    • quit.GetQuitEvent().AddGoroutine()
  • 停止协程 减计数 
    • quit.GetQuitEvent().DoneGoroutine()
  • 常驻后台运行的协程退出的条件改成退出事件是否结束的条件 
    • !quit.GetQuitEvent().HasFired()
  • 常驻后台运行的协程若通过 select 处理 chan,同时增加退出事件的chan
    • case <-quit.GetQuitEvent().Done()
// myWorker my worker
type myWorker struct {
} // RunWorkerWithChan run Goroutine worker
func (m *myWorker) RunWorkerWithChan() {
// 启动一个Goroutine时,增加Goroutine数
quit.GetQuitEvent().AddGoroutine()
defer func() {
// 一个Goroutine退出时,减少Goroutine数
quit.GetQuitEvent().DoneGoroutine()
}()
// 退出时,此次退出
for !quit.GetQuitEvent().HasFired() {
select {
// 退出时,收到退出信号
case <-quit.GetQuitEvent().Done():
break
//case msg := <- m.YouChan:
// handle msg
}
}
} // RunWorker run Goroutine worker
func (m *myWorker) RunWorker() {
// 启动一个Goroutine时,增加Goroutine数
quit.GetQuitEvent().AddGoroutine()
defer func() {
// 一个Goroutine退出时,减少Goroutine数
quit.GetQuitEvent().DoneGoroutine()
}() // 退出时,此次退出
for !quit.GetQuitEvent().HasFired() {
// ...
}
}

  

2.4 实现 io.Closer 接口的自定义服务平滑关闭

实现 io.Closer 接口的结构体,增加到退出事件处理器中 

// startMyService start my service
func startMyService() {
srv := NewMyService()
// 注册平滑关闭,退出时会调用 srv.Close()
quit.GetQuitEvent().RegisterCloser(srv)
srv.Run()
} // myService my service
type myService struct {
isStop bool
} // NewMyService new
func NewMyService() *myService {
return &myService{}
} // Close my service
func (m *myService) Close() error {
m.isStop = true
return nil
} // Run my service
func (m *myService) Run() {
for !m.isStop {
// ....
}
}

  

2.5 集成其他框架怎么做

退出信号处理由某一框架接管,寻找框架如何注册退出函数,优秀的框架一般都会实现安全实现退出的机制。

如下将退出事件注册到某一框架的平滑关闭函数中

func startMyServer() {
// ...
// xxx框架退出函数注册退出事件
xxx.RegisterQuitter(func() {
quit.GetQuitEvent().GracefulStop()
})
}

参考:

https://github.com/mygityf/go-library/blob/main/quit/quit.go

完。

祝玩的开心~

【Golang】程序如何优雅的退出?的更多相关文章

  1. 如何优雅地退出python程序

    如何优雅地退出python程序 一个单模的python程序,启动之后要能够优雅地关闭.即当用户按Ctrl+C或者kill pid的时候,程序都能从容关闭.实现起来非常简单. is_running = ...

  2. C# Note11:如何优雅地退出WPF应用程序

    前言 I should know how I am supposed to exit my application when the user clicks on the Exit menu item ...

  3. 从nsq中学习如何优雅的退出go 网络程序

    退出运行中的程序,可以粗暴的kill -9 $PID,但这样会破坏业务的完整性,有可能一个正在在执行的逻辑半途而费,从而产生不正常的垃圾数据. 本文总结在go语言中,如何能优雅的退出网络应用,涉及的知 ...

  4. Android程序的隐藏与退出

    转自Android程序的隐藏与退出 Android的程序无需刻意的去退出,当你一按下手机的back键的时候,系统会默认调用程序栈中最上层Activity的Destroy()方法来销毁当前Activit ...

  5. 情景linux--如何优雅地退出telnet

    情景linux--在脚本中如何优雅地退出telnet 情景 telnet命令是TELNET协议的用户接口,它支持两种模式:命令模式和会话模式.虽然telnet支持许多命令,但大部分情况下,我们只是使用 ...

  6. python 如何优雅地退出子进程

    python 如何优雅地退出子进程 主进程产生子进程,子进程进入永久循环模式.当主进程要求子进程退出时,如何能安全地退出子进程呢? 参考一些代码,我写了这个例子.运行之后,用kill pid试试.pi ...

  7. 在MacOS上使用gdb(cgdb)调试Golang程序

    如果你在MacOS上使用GDB工具载入Golang程序时无法载入,这篇文章可以解决.本文不具体介绍调试的方法,网上的文章太多了就不赘述了. cgdb使用的是gdb的内核,方法和原理试用本文. 问题分析 ...

  8. 如何优雅的退出/关闭/重启gunicorn进程

    在工作中,会发现gunicorn启动的web服务,无论怎么使用kill -9 进程号都是无法杀死gunicorn,经过我一番百度和谷歌,发现想要删除gunicorn进程其实很简单. 1. 寻找mast ...

  9. CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中

    CentOS7中_带sqlite3_CGO的golang程序_交叉编译到arm中 转载注明来源: 本文链接 来自osnosn的博客,写于 2019-10-28. 编写了个golang程序,用到了这个C ...

随机推荐

  1. Ncrystal Skill设计

    在使用allegro时一般都会听说过skill,使用合适的Skill会使事情事半功倍.但是现阶段所能看到的个人白嫖的Skill都有一些通病.所以我才开发符合自己操作习惯的Skill. 当前我们所能找的 ...

  2. 2020西湖论剑一道web题[网盘]

    题目: 一个网盘系统 图片: 解题手法 上传".htaccess"文件,改成可以执行lua脚本 内容为: SetHandler lua-script 编写lua脚本,而后进行get ...

  3. 如何在 Java 中实现无向环和有向环的检测

    无向环 一个含有环的无向图如下所示,其中有两个环,分别是 0-2-1-0 和 2-3-4-2: 要检测无向图中的环,可以使用深度优先搜索.假设从顶点 0 出发,再走到相邻的顶点 2,接着走到顶点 2 ...

  4. linux系统从pci.ids文件获取硬件设备详细厂商信息

    机器采样: [root@ht24 hwdata]# cat /etc/redhat-release ; uname -r CentOS Linux release 7.9.2009 (Core) 3. ...

  5. maven打包jar到本地仓库

    1.执行如下命令 mvn install:install-file -Dfile=guava-28.2-jre.jar -DgroupId=com.google.guava -DartifactId= ...

  6. 几种比较经典的波形及其FFT变换(正弦波,三角波,方波和锯齿波)

    之前上学时我的信号学得最差了,主要原因还是我高数学得不怎么样.可能是人总敬畏自己最不会的,所以我觉得我学过诸多科目中,数学是最博大精深而最妙的,从最开始的一次函数到反比例函数,二次三次函数和双曲线,椭 ...

  7. 《手把手教你》系列基础篇(八十八)-java+ selenium自动化测试-框架设计基础-Log4j 2实现日志输出-下篇(详解教程)

    1.简介 上一篇宏哥讲解和分享了如何在控制台输出日志,但是你还需要复制粘贴才能发给相关人员,而且由于界面大小限制,你只能获取当前的日志,因此最好还是将日志适时地记录在文件中直接打包发给相关人员即可.因 ...

  8. docker容器的持久化存储:Volume

    独立于docker容器的持久化存储: 法(1):自动将服务器文件夹挂载到容器内部文件夹/usr/share/nginx/html,这样只修改服务器文件夹下的内容即可对应修改容器内部文件夹的内容 将服务 ...

  9. python学习-Day18

    目录 今日内容详细 模块 循环导入问题 判断文件类型 py文件可以被分为两种类型 内置变量 __ name __ 模块的查找顺序 验证先从内存空间中查找 验证再从内置模块中查找 验证sys.path ...

  10. Go语言学习——指针、new和make

    指针 Go语言中不存在指针操作,只需要记住两个符号: &:取地址 *:根据地址取值 vscode 打开多个标签页 settings.json中输入"workbench.editor. ...