Golang-如何实现服务的优雅关停/更新
背景
如果我们的应用在kubernetes上我们可以通过滚动更新,每次只更新一部分副本实现服务的不停机更新发布,那如果我们的项目不依托于kubernetes想要达到这种效果该如何实现呢 ?
我们想要达到的效果:
- 不关闭现有连接(正在运行中的程序)
- 新的进程启动并替代旧进程
- 新的进程接管新的连接
- 连接要随时响应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不会出现拒绝请求的情况
方案
什么是信号?
在Linux中,信号其实就是软中断,用来通知进程发生了事件。进程之间可以通过调用kill库函数发送软中断信号。Linux内核也可能给进程发送信号,通知进程发生了某个事件(例如内存越界)。注意,信号只是用来通知某进程发生了什么事件,无法给进程传递任何数据。
进程对信号的处理方法有三种:
- 第一种方法是,忽略某个信号,对该信号不做任何处理,就像未发生过一样。
- 第二种是设置中断的处理函数,收到信号后,由该函数来处理。
- 第三种方法是,对该信号的处理采用系统的默认操作,大部分的信号的默认操作是终止进程。
1 |
|
信号的相关解释
Signal Name | Number | Description | Signal Name | Number | Description |
---|---|---|---|---|---|
SIGHUP | 1 | Hangup (POSIX) 终端控制进程结束(终端连接断开) | SIGSTKFLT | 16 | Stack fault |
SIGINT | 2 | Terminal interrupt (ANSI) | SIGCHLD | 17 | Child process has stopped or exited, changed (POSIX) |
SIGQUIT | 3 | Terminal quit (POSIX) | SIGCONT | 18 | Continue executing, if stopped (POSIX) |
SIGILL | 4 | Illegal instruction (ANSI) | SIGSTOP | 19 | Stop executing(can’t be caught or ignored) (POSIX) |
SIGTRAP | 5 | Trace trap (POSIX) | SIGTSTP | 20 | Terminal stop signal (POSIX) |
SIGIOT | 6 | IOT Trap (4.2 BSD) | SIGTTIN | 21 | Background process trying to read, from TTY (POSIX) |
SIGBUS | 7 | BUS error (4.2 BSD) | SIGTTOU | 22 | Background process trying to write, to TTY (POSIX) |
SIGFPE | 8 | Floating point exception (ANSI) | SIGURG | 23 | Urgent condition on socket (4.2 BSD) |
SIGKILL | 9 | Kill(can’t be caught or ignored) (POSIX) | SIGXCPU | 24 | CPU limit exceeded (4.2 BSD) |
SIGUSR1 | 10 | User defined signal 1 (POSIX) | SIGXFSZ | 25 | File size limit exceeded (4.2 BSD) |
SIGSEGV | 11 | Invalid memory segment access (ANSI) | SIGVTALRM | 26 | Virtual alarm clock (4.2 BSD) |
SIGUSR2 | 12 | User defined signal 2 (POSIX) | SIGPROF | 27 | Profiling alarm clock (4.2 BSD) |
SIGPIPE | 13 | Write on a pipe with no reader, Broken pipe (POSIX) | SIGWINCH | 28 | Window size change (4.3 BSD, Sun) |
SIGALRM | 14 | Alarm clock (POSIX) | SIGIO | 29 | I/O now possible (4.2 BSD) |
SIGTERM | 15 | Termination (ANSI) | SIGPWR | 30 | Power failure restart (System V) |
我们可以通过监听信号量的变化来实现关闭前的一些操作。一个可行的方案如下:
为了方便我们称旧应用为old server,需要更新的应用为new server
- old server 监听 SIGHUP 信号;
- old server 收到信号SIGHUP时 fork 子进程(使用相同的启动命令),将服务监听的 socket 文件描述符传递给子进程 new server;
- 子进程(new server)监听父进程(new server)的 socket,这个时候父进程和子进程都可以接收请求;
- 子进程启动成功之后发送 SIGTERM 信号给父进程,父进程停止接收新的连接,等待旧连接处理完成(或超时);
- 父进程退出,此时只剩下子进程,丝滑升级完成;
编码
以下案例是比较粗糙的案例,如果是想生产环境使用建议参考文末的资料链接,有相关成熟的开源组件
1 |
|
实践
1.启动服务
1 |
|
2.开启两个或者两个以上请求接口
1 |
|
3.服务端打印信息显示我们的请求正在处理
1 |
|
3.发送信号结束进程
1 |
|
4.查看控制台,发现我们的服务还在进行中,并未立即结束
1 |
|
5.查看进程,发现开启了两个server进程(一个是子进程),当请求执行完父进程退出,只剩下子进程
1 |
|
资料
Golang-如何实现服务的优雅关停/更新
https://mikeygithub.github.io/2022/11/24/yuque/Golang篇-如何实现服务的优雅关停!更新/