本文目标

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

ctrl + c

SIGPIPE
 

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

命令信号含义
dump core
因此在我们执行
ctrl + c
关闭
gin
服务端时,会强制进程结束,导致正在访问的用户等出现问题 常见的 
kill -9 pid
 会发送 
SIGKILL
 信号给进程,也是类似的结果

信号

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

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

所有信号

 

  •  
$ kill -l

 

 1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

 

怎样算优雅

  • 不关闭现有连接(正在运行中的程序)

  • 新的进程启动并替代旧进程

  • 新的进程接管新的连接

  • 连接要随时响应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不可以出现拒绝请求的情况

流程

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

2、发送信号量 
SIGHUP
3、拒绝新连接请求旧进程,但要保证已有连接正常4、启动新的子进程5、新的子进程开始 
Accet
6、系统将新的请求转交新的子进程7、旧进程处理完所有旧连接后正常结束

实现优雅重启endless

Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3+)

我们借助 fvbock/endless 来实现 
Golang HTTP/HTTPS
 服务重新启动的零停机
endless server
 监听以下几种信号量:
forkhammerTime
endless
 正正是依靠监听这些信号量,完成管控的一系列动作

安装

 

  •  
go get -u github.com/fvbock/endless

 

编写

打开 gin-blog 的 
main.go
文件,修改文件:
package main

import (
"fmt"
"log"
"syscall"

"github.com/fvbock/endless"

"gin-blog/routers"
"gin-blog/pkg/setting"
)

func main() {
endless.DefaultReadTimeOut = setting.ReadTimeout
endless.DefaultWriteTimeOut = setting.WriteTimeout
endless.DefaultMaxHeaderBytes = 1 << 20
endPoint := fmt.Sprintf(":%d", setting.HTTPPort)

server := endless.NewServer(endPoint, routers.InitRouter())
server.BeforeBegin = func(add string) {
log.Printf("Actual pid is %d", syscall.Getpid())
}

err := server.ListenAndServe()
if err != nil {
log.Printf("Server err: %v", err)
}
}
endless.NewServer
 返回一个初始化的 
endlessServer
 对象,在 
BeforeBegin
 时输出当前进程的 
pid
,调用 
ListenAndServe
 将实际“启动”服务

 

验证编译

 

  •  
$ go build main.go

 

执行

 

  •  
$ ./main

 

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production....Actual pid is 48601
启动成功后,输出了
pid
为 48601;在另外一个终端执行 
kill -1 48601
 ,检验先前服务的终端效果
[root@localhost go-gin-example]# ./main
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET /auth --> ...
[GIN-debug] GET /api/v1/tags --> ...
...

Actual pid is 48601

...

Actual pid is 48755
48601 Received SIGTERM.
48601 [::]:8000 Listener closed.
48601 Waiting for connections to finish...
48601 Serve() returning...
Server err: accept tcp [::]:8000: use of closed network connection
可以看到该命令已经挂起,并且 
fork
 了新的子进程 
pid
 为 
48755
48601 Received SIGTERM.
48601 [::]:8000 Listener closed.
48601 Waiting for connections to finish...
48601 Serve() returning...
Server err: accept tcp [::]:8000: use of closed network connection
大致意思为主进程(
pid
为 48601)接受到 
SIGTERM
 信号量,关闭主进程的监听并且等待正在执行的请求完成;这与我们先前的描述一致
唤醒
postman
Actual pid is 48755
48601 Received SIGTERM.
48601 [::]:8000 Listener closed.
48601 Waiting for connections to finish...
48601 Serve() returning...
Server err: accept tcp [::]:8000: use of closed network connection


$ [GIN] 2018/03/15 - 13:00:16 | 200 | 188.096µs | 192.168.111.1 | GET /api/v1/tags...

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

 

问题

endless
Golang >= 1.8http.Server
package main

import (
"fmt"
"net/http"
"context"
"log"
"os"
"os/signal"
"time"


"gin-blog/routers"
"gin-blog/pkg/setting"
)

func main() {
router := routers.InitRouter()

s := &http.Server{
Addr: fmt.Sprintf(":%d", setting.HTTPPort),
Handler: router,
ReadTimeout: setting.ReadTimeout,
WriteTimeout: setting.WriteTimeout,
MaxHeaderBytes: 1 << 20,
}

go func() {
if err := s.ListenAndServe(); err != nil {
log.Printf("Listen: %s\n", err)
}
}()

quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<- quit

log.Println("Shutdown Server ...")

ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
defer cancel()
if err := s.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}

log.Println("Server exiting")
}

 

小结

在日常的服务中,优雅的重启(热更新)是非常重要的一环。

GolangHTTP

 

关注我,获资源干货

如何优雅地重启服务?_重启