Go中如何优雅的关闭程序
date
Nov 9, 2023
slug
graceful-shut-down-a-program-in-Go
status
Published
tags
编程开发
summary
做后台开发的都知道,当我们需要对服务进行重启,例如升级发版、异常重启时,会面临到有新的请求在不断进来,同时当前可能还有请求还未处理完成,如果此时直接对服务进行关闭重启,是有一定风险的。尤其是一些写操作,可能会造成数据丢失或损害。
正因如此我们应该等到请求都处理完成了才进行程序的退出,本文接下来就来介绍下在go语言中,针对http服务的场景如何进行优雅的程序退出。
type
Post
做后台开发的都知道,当我们需要对服务进行重启,例如升级发版、异常重启时,会面临到有新的请求在不断进来,同时当前可能还有请求还未处理完成,如果此时直接对服务进行关闭重启,是有一定风险的。尤其是一些写操作,可能会造成数据丢失或损害。
正因如此我们应该等到请求都处理完成了才进行程序的退出,本文接下来就来介绍下在go语言中,针对http服务的场景如何进行优雅的程序退出。
我们先用go实现一个简单的http服务。
我们实现了一个模拟耗时请求的handler,如果此时我们直接关闭服务的话,这个耗时的请求将没法得到完整的处理。既然如此,我们需要先让程序知道,接下来程序要退出了,但是你先别着急退出。
那怎么知道呢,其实操作系统会在此时向程序发送一个SIGTERM的信号,那我们只需要让程序捕获这个信号就可以了。来看下实现:
在上面main函数代码中,可以看到几点变化:
- 我们通过
signal.Notify()
监听了两个程序退出信号,当接收到信号后则会被发送到shutdownListener
管道中 - SIGINT:当你在终端按下ctrl+c时,则会触发这个信号
- SIGTERM:当我们给程序发送kill或者killall指令时,则会触发这个信号
- 执行
srv.ListenAndServe()
后,程序是会被阻塞在这行代码中的。为了避免后续的信号监测的代码得到执行,我们需要将其放到协程中执行。
- 我们将
srv.ListenAndServe()
出错的退出处理os.Exit(1)
去掉了,也通过管道通知的方式,统一在最外层的主协程中统一来处理。需要注意,这里不处理服务关闭这个错误(ErrServerClosed),后面会解释。
到这里我们已经实现了通知我们的服务程序要退出这个信号了,接下来我们要让程序真正的优雅退出。有两点要求:
- 让服务不再接收新的请求。
- 尽可能晚的退出程序,让正在处理中的请求尽可能的都处理完。
go的http网络库中提供了
Shutdown
方法来拒绝新的请求,而已经接收到的请求并不会被关闭,而是会等待其处理完成再关闭。同时Serve
、ListenAndServe
和 ListenAndServeTLS
会立即返回 ErrServerClosed错误,因此我们不能在接收到这个错误后关闭程序。来看下代码实现
我们实现了一个优雅退出的函数
gracefulShutdown()
,代码不难理解,需要注意Shutdown方法需要接收一个context对象,这里我们定义了一个10秒超时的context,如果超过这个时间请求都还没完成处理,则会采用强制退出的方式,主要是为了避免程序长时间等待无法退出,当年你也可以选择传入一个没有超时的context(例如直接传入context.Background())。至此我们已经实现了一个程序优雅退出的功能了,当然你还可以继续往
gracefulShutdown()
中添加其他退出前需要完成的操作,例如数据库和消息队列等连接断开,上报程序关闭日志等等,这些我们就不赘述了。接下来贴出完整的代码。