<h2>前言</h2>

每次更新完代码,更新完配置文件后 就直接这么 <code>ctrl c</code> 真的没问题吗,<code>ctrl c</code>到底做了些什么事情呢?

在这一节中我们简单讲述 <code>ctrl c</code> 背后的信号以及如何在<code>Gin</code>中优雅的重启服务,也就是对 <code>HTTP</code> 服务进行热更新

项目地址:https://github.com/EDDYCJY/go-gin-example 引用的别人的项目

<h2>ctrl c</h2> <blockquote> 内核在某些情况下发送信号,比如在进程往一个已经关闭的管道写数据时会产生 <code>SIGPIPE</code>信号 </blockquote>

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

<table><tbody><tr><th>命令</th><th>信号</th><th>含义</th></tr></tbody></table>

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

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

<h3>信号</h3>

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

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

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

<h3>所有信号</h3> <h2>怎样算优雅</h2> <h3>目的</h3> <ul><li>不关闭现有连接(正在运行中的程序)</li><li>新的进程启动并替代旧进程</li><li>新的进程接管新的连接</li><li>连接要随时响应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不可以出现拒绝请求的情况</li></ul><h3>流程</h3>

1、替换可执行文件或修改配置文件

2、发送信号量 <code>SIGHUP</code>

3、拒绝新连接请求旧进程,但要保证已有连接正常

4、启动新的子进程

5、新的子进程开始 <code>Accet</code>

6、系统将新的请求转交新的子进程

7、旧进程处理完所有旧连接后正常结束

<h2>实现优雅重启</h2> <h3>endless</h3> <blockquote> Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3 ) </blockquote>

我们借助 fvbock/endless 来实现 <code>Golang HTTP/HTTPS</code> 服务重新启动的零停机

<code>endless server</code> 监听以下几种信号量:

<ul><li>syscall.SIGHUP:触发 <code>fork</code> 子进程和重新启动</li><li>syscall.SIGUSR1/syscall.SIGTSTP:被监听,但不会触发任何动作</li><li>syscall.SIGUSR2:触发 <code>hammerTime</code></li><li>syscall.SIGINT/syscall.SIGTERM:触发服务器关闭(会完成正在运行的请求)</li></ul>

<code>endless</code> 正正是依靠监听这些信号量,完成管控的一系列动作

<h3>安装</h3> <h3>编写</h3>

打开 gin-blog 的 <code>main.go</code>文件,修改文件:

<code>endless.NewServer</code> 返回一个初始化的 <code>endlessServer</code> 对象,在 <code>BeforeBegin</code> 时输出当前进程的 <code>pid</code>,调用 <code>ListenAndServe</code> 将实际“启动”服务

<h3>验证</h3> <h3>编译</h3> <h3>执行</h3>

启动成功后,输出了<code>pid</code>为 48601;在另外一个终端执行 <code>kill -1 48601</code> ,检验先前服务的终端效果

可以看到该命令已经挂起,并且 <code>fork</code> 了新的子进程 <code>pid</code> 为 <code>48755</code>

大致意思为主进程(<code>pid</code>为48601)接受到 <code>SIGTERM</code> 信号量,关闭主进程的监听并且等待正在执行的请求完成;这与我们先前的描述一致

<h3>唤醒</h3>

这时候在 <code>postman</code> 上再次访问我们的接口,你可以惊喜的发现,他“复活”了!

这就完成了一次正向的流转了

你想想,每次更新发布、或者修改配置文件等,只需要给该进程发送SIGTERM信号,而不需要强制结束应用,是多么便捷又安全的事!

<h3>问题</h3>

<code>endless</code> 热更新是采取创建子进程后,将原进程退出的方式,这点不符合守护进程的要求

<h3>http.Server - Shutdown()</h3>

如果你的<code>Golang >= 1.8</code>,也可以考虑使用 <code>http.Server</code> 的 Shutdown 方法

<h2>小结</h2>

在日常的服务中,优雅的重启(热更新)是非常重要的一环。而 <code>Golang</code> 在 <code>HTTP</code> 服务方面的热更新也有不少方案了,我们应该根据实际应用场景挑选最合适的

感谢:https://www.kancloud.cn/shuangdeyu/gin_book/949445