Contents

实现一个Web服务-(二)

实现一个Web服务-(一) 从 net/http 基础网络库中实现了一个 Web Server,基本实现了路由匹配、上下文、中间件等特性。除这些基本的功能外,还有一些更高阶的功能。比如优雅关闭、日志等功能。

优雅关闭(Graceful Close)

对于一个服务而言,功能和需求的开发一定是不断迭代的,在迭代的过程中,服务不可避免的会存在关闭、启动这样的动作。假设当前的服务是一个非常繁忙的服务,一个不优雅的关闭,请求的进程中断,导致用户的请求出现异常,这个在普通的博客网站没什么影响,但是如果是和支付相关的业务就存在很严重的损失。

所以,优雅关闭服务的本质近视关闭进程时不能暴力关闭进程,而是等该进程的所有请求逻辑处理完成之后再关闭。那么问题的本质就是 “控制关闭进程的操作”“如何等多所有的逻辑结束”

如何控制接管关闭的操作

信号

在终端,非守护进程可以使用 Ctrl+C 的方式结束一个程序,其本质是发送了一个 SIGINT 信号, Ctrl+\ 向进程发送 SIGQUIT 信号,也是可以被阻塞处理,唯一的不同的是,默认行为会产生 core 文件。kill -9 pid 会给对应 pid 的进程发送 SIGKILL 信号,kill pid 会向进程发送 SIGTERM 信号。

信号操作是否可以被处理
SIGINTCtrl+C可以
SIGQUITCtrl + \可以
SIGTERMkill可以
SIGKILLkill -9不可以

os/signal 库

go 提供了捕获信号的方法,提供如下方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

// 忽略某个信号
func Ignore(sig ...os.Signal){}
// 判断某个信号是否被忽略了
func Ignored(sig os.Signal) bool{}
// 关注某个/某些/全部 信号
func Notify(c chan<- os.Signal, sig ...os.Signal){}
// 取消使用 notify 对信号产生的效果
func Reset(sig ...os.Signal){}
// 停止所有向 channel 发送的效果
func Stop(c chan<- os.Signal){}

因为使用 Ctrl 或者 kill 命令,它们发送的信号是进入 main 函数的,所以即只有 main 函数所在的 Goroutine 监听信号。

Golang 官方库实现

在 Golang 1.8 版本之前, net/http 是没法提供方法的,所以当时开源社区涌现了不少第三方解决方案:gracefulgrace

其基本的思路差不多: 设计一个监听事件函数,监听事件结束后,通过 channel 等机制来等待主流程结束。

而在 1.8 版本之后。 net/http 引入了 server.Shutdown() 函数来进行优雅重启。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
func (srv *Server) Shutdown(ctx context.Context) error {
	srv.inShutdown.setTrue()

	srv.mu.Lock()
	lnerr := srv.closeListenersLocked()
	srv.closeDoneChanLocked()
	for _, f := range srv.onShutdown {