01
介绍
系统信号是在类 Unix 系统中用来进程间通讯的一种方式。我们可以使用 kill -l
命令查看各个系统支持的信号列表,每个信号都有名称和编号。我们可以使用 kill 命令给特定进程发送指定信号名称或信号编号的系统信号。
系统信号分为同步信号和异步信号。其中同步信号是程序执行中的错误触发的信号,在 Golang 程序中,同步信号通常会被转换为 runtime panic,异步信号是系统内核或其它程序发送的信号。
关于系统信号的更多内容,感兴趣的读者朋友可以自行检索相关资料学习。本文我们主要介绍怎么使用 Golang 语言拦截系统信号和怎么实现优雅退出 http server。
02
Golang 标准库 os/signal
关于如何使用 Golang 语言拦截系统信号的问题,Golang 在标准库 os/signal
包中,提供了几个函数,可以拦截系统信号。我们重点介绍 Notify 函数。
func Notify(c chan<- os.Signal, sig ...os.Signal)
os/signal
包的 Notify 函数将输入信号中继到 channel c。如果未指定信号(sig 参数为空),则所有输入信号都将中继到 channel c。否则,将仅拦截指定的信号。
os/signal
包将不会阻塞发送输入信号到 channel c,Notify 函数调用方必须确保 channel c 有足够的缓冲区空间,以跟上预期的信号速率。对于仅用于通知一个信号值的 channel,大小为 1 的缓冲区就足够了。
接收指定信号的示例代码:
func main() {
// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// Block until a signal is received.
s := <-c
fmt.Println("Got signal:", s)
}
接收所有信号的示例代码:
package main
import (
"fmt"
"os"
"os/signal"
)
func main() {
// Set up channel on which to send signal notifications.
// We must use a buffered channel or risk missing the signal
// if we're not ready to receive when the signal is sent.
c := make(chan os.Signal, 1)
// Passing no signals to Notify means that
// all signals will be sent to the channel.
signal.Notify(c)
// Block until any signal is received.
s := <-c
fmt.Println("Got signal:", s)
}
03
拦截系统信号并优雅退出 http server
我们可以使用 os/signal
包的 Notify 函数拦截系统信号,并通过 http.Server
的 Shutdown 方法优雅退出 http server。
func (srv *Server) Shutdown(ctx context.Context) error
在 Golang 1.8 中新增的 Shutdown 方法可以在不中断任何活动连接的情况下正常关闭服务器。Shutdown 的工作方式是先关闭所有打开的监听器,然后关闭所有空闲连接,然后等待所有活跃连接为空闲状态时,关闭服务器。
如果提供的上下文在关闭完成之前已超时,则 Shutdown 返回上下文的错误,否则它将返回从关闭服务器的监听器返回的错误。
调用 Shutdown 时,Serve,ListenAndServe 和 ListenAndServeTLS 立即返回 ErrServerClosed。确保 Shutdown 未返回时,程序没有退出。
需要注意的是,Shutdown 不会尝试关闭也不等待长连接,例如 WebSockets。如果需要,Shutdown 的调用者应单独通知此类长连接,并等待它们关闭。
一旦调用了 Server 的 Shutdown 方法,server 就无法使用了。如果再调用 Serve 的方法将返回 ErrServerClosed。
优雅退出 http server 的示例代码如下:
func main() {
// 优雅退出
http.HandleFunc("/", hello)
server := http.Server{Addr: ":8080"}
go func() {
if err := server.ListenAndServe(); err != nil {
fmt.Println("server start failed")
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
s := <-c
fmt.Printf("接收信号:%s\n", s)
ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
fmt.Println("server shutdown failed")
}
fmt.Println("server exit")
}
func hello (w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "Hello Go!")
}
阅读上面这段代码,可以发现我们拦截到系统信号 SIGNINT 后,通过 sleep 模拟程序还未执行结束(比如需要执行一些收尾工作),此时,系统未直接终止该应用进程(直接终止是系统默认处理信号 SIGINT 的方式),而是等待程序执行结束后,系统才终止该应用进程。
04
总结
本文我们主要介绍了 Golang 语言怎么拦截系统信号,和使用 os/signal
包的 Notify 函数,结合 net/http
包中 http.Server
的 Shutdown
方法,实现优雅退出 http server。
推荐阅读:
Golang 语言三方库 lumberjack 日志切割组件怎么使用?
参考资料: https://zh.wikipedia.org/wiki/Unix信号 https://golang.org/pkg/os/signal/#Notify https://golang.org/pkg/net/http/#Server.Shutdown