Golang 优雅的终止一个服务


Golang 优雅的终止一个服务

文章插图
 
采用常规方式启动一个Golang http服务时 , 若服务被意外终止或中断 , 即未等待服务对现有请求连接处理并正常返回且亦未对服务停止前作一些必要的处理工作 , 这样即会造成服务硬终止 。这种方式不是很优雅 。
参看如下代码 , 该http服务请求路径为根路径 , 请求该路径 , 其会在2s后返回hello 。
var addr = flag.String("server addr", ":8080", "server address")func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { time.Sleep(2 * time.Second) fmt.Fprintln(w, "hello") }) http.ListenAndServe(*addr, nil)}若服务启动后 , 请求http://localhost:8080/ , 然后使用Ctrl+C立即中断服务 , 服务即会立即退出(exit status 2) , 请求未正常返回(ERR_CONNECTION_REFUSED) , 连接即马上断了 。
接下来介绍使用http.Server的Shutdown方法结合signal.Notify来优雅的终止服务 。
1 Shutdown方法Golang http.Server结构体有一个终止服务的方法Shutdown , 其go doc如下 。
func (srv *Server) Shutdown(ctx context.Context) error Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down. If the provided context expires before the shutdown is complete, Shutdown returns the context's error, otherwise it returns any error returned from closing the Server's underlying Listener(s). When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return ErrServerClosed. Make sure the program doesn't exit and waits instead for Shutdown to return. Shutdown does not attempt to close nor wait for hijacked connections such as WebSockets. The caller of Shutdown should separately notify such long-lived connections of shutdown and wait for them to close, if desired. See RegisterOnShutdown for a way to register shutdown notification functions. Once Shutdown has been called on a server, it may not be reused; future calls to methods such as Serve will return ErrServerClosed.由文档可知:
使用Shutdown可以优雅的终止服务 , 其不会中断活跃连接 。
其工作过程为:首先关闭所有开启的监听器 , 然后关闭所有闲置连接 , 最后等待活跃的连接均闲置了才终止服务 。
若传入的context在服务完成终止前已超时 , 则Shutdown方法返回context的错误 , 否则返回任何由关闭服务监听器所引起的错误 。
当Shutdown方法被调用时 , Serve、ListenAndServe及ListenAndServeTLS方法会立刻返回ErrServerClosed错误 。请确保Shutdown未返回时 , 勿退出程序 。
对诸如WebSocket等的长连接 , Shutdown不会尝试关闭也不会等待这些连接 。若需要 , 需调用者分开额外处理(诸如通知诸长连接或等待它们关闭 , 使用RegisterOnShutdown注册终止通知函数) 。
一旦对server调用了Shutdown , 其即不可再使用了(会报ErrServerClosed错误) 。
有了Shutdown方法 , 我们知道在服务终止前 , 调用该方法即可等待活跃连接正常返回 , 然后优雅的关闭 。
关于上面用到的Golang Context参数 , 之前专门写过一篇文章介绍了Context的使用场景(请参考:Context 你使用过了吧?本文和你一起总结 Golang Context 的使用) 。
但服务启动后的某一时刻 , 程序如何知道服务被中断了呢?服务被中断时如何通知程序 , 然后调用Shutdown作处理呢?接下来看一下系统信号通知函数的作用 。
2 signal.Notify函数signal包的Notify函数提供系统信号通知的能力 , 其go doc如下 。
func Notify(c chan<- os.Signal, sig ...os.Signal) Notify causes package signal to relay incoming signals to c. If no signals are provided, all incoming signals will be relayed to c. Otherwise, just the provided signals will. Package signal will not block sending to c: the caller must ensure that c has sufficient buffer space to keep up with the expected signal rate. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient. It is allowed to call Notify multiple times with the same channel: each call expands the set of signals sent to that channel. The only way to remove signals from the set is to call Stop. It is allowed to call Notify multiple times with different channels and the same signals: each channel receives copies of incoming signals independently.


推荐阅读