关于golang服务重启

我相信每个人都会想,每次更新完代码,更新完配置文件后,就直接这么 ctrl+c 真的没问题吗,ctrl+c到底做了些什么事情呢?

本文我们讨论 ctrl+c 背后的信号以及如何优雅的重启服务,以及对 HTTP 服务进行热更新。

ctrl + c

在终端执行特定的组合键可以使系统发送特定的信号给此进程,完成一系列的动作

命令 信号 含义
ctrl + c SIGINT 强制进程结束
ctrl + z SIGTSTP 任务中断,进程挂起
ctrl + \ SIGQUIT 进程结束 和 dump core
ctrl + d EOF
常用于重启、重新加载进程 SIGHUP 若程序中没有捕捉该信号,当收到该信号时,进程就会退出
SIGPIPE 在进程往一个已经关闭的管道写数据时会产生

因此在我们执行ctrl + c关闭服务端时,会强制进程结束,导致正在访问的用户等出现问题

常见的 kill -9 pid 会发送 SIGKILL 信号给进程,也是类似的结果。

信号

本段中出现信号是什么呢?

信号是 Unix 、类 Unix 以及其他 POSIX 兼容的操作系统中进程间通讯的一种有限制的方式。

它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程。此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

所有信号

从go语言 go root SDK中 zerrors_linux_amd64.go 来看,关于信号有如下常量:

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
SIGABRT   
SIGALRM
SIGBUS
SIGCHLD
SIGCLD
SIGCONT
SIGFPE
SIGHUP
SIGILL
SIGINT
SIGIO
SIGIOT
SIGKILL
SIGPIPE
SIGPOLL
SIGPROF
SIGPWR
SIGQUIT
SIGSEGV
SIGSTKFLT
SIGSTOP
SIGSYS
SIGTERM
SIGTRAP
SIGTSTP
SIGTTIN
SIGTTOU
SIGUNUSED
SIGURG
SIGUSR1
SIGUSR2
SIGVTALRM
SIGWINCH
SIGXCPU
SIGXFSZ

怎样算优雅

优雅地退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TrapSignal catches the SIGTERM/SIGINT/SIGKILL and executes cb function. 
// After that it exits with code 0.
func TrapSignal(cb func()) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
go func() {
for sig := range c {
fmt.Sprintf("captured %v, exiting...", sig)
if cb != nil {
cb()
}
os.Exit(0)
}
}()
}

在接收退出信号SIGTERM/SIGINT/SIGKILL之后,系统退出os.Exit(0)之前,执行cb() callback函数。具体执行方法建议如下:

1
2
3
4
5
6
7
8
9
10
11
func Cmd(ctx *cli.Context) error {
// program of business logic
start()

TrapSignal(logger, func() {
// program of business exit here
stop()
})

return nil
}

gin退出

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
engine := gin.Default()
engine.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
TrapSignal(func() {
fmt.Println("before exit.")
})

engine.Run()
}