在前面编写案例代码时,我相信你会想到html
每次更新完代码,更新完配置文件后
就直接这么 ctrl+c
真的没问题吗,ctrl+c
到底作了些什么事情呢?git
在这一节中咱们简单讲述 ctrl+c
背后的信号以及如何在Gin
中优雅的重启服务,也就是对 HTTP
服务进行热更新github
原文地址:Golang优雅重启HTTP服务
项目地址:https://github.com/EDDYCJY/go...golang
内核在某些状况下发送信号,好比在进程往一个已经关闭的管道写数据时会产生
SIGPIPE
信号
在终端执行特定的组合键可使系统发送特定的信号给此进程,完成一系列的动做segmentfault
命令 | 信号 | 含义 |
---|---|---|
ctrl + c | SIGINT | 强制进程结束 |
ctrl + z | SIGTSTP | 任务中断,进程挂起 |
ctrl + \ | SIGQUIT | 进程结束 和 dump core |
ctrl + d | EOF | |
SIGHUP | 终止收到该信号的进程。若程序中没有捕捉该信号,当收到该信号时,进程就会退出(经常使用于 重启、从新加载进程) |
所以在咱们执行ctrl + c
关闭gin
服务端时,会强制进程结束,致使正在访问的用户等出现问题api
常见的 kill -9 pid
会发送 SIGKILL
信号给进程,也是相似的结果缓存
本段中反复出现信号是什么呢?安全
信号是 Unix
、类 Unix
以及其余 POSIX
兼容的操做系统中进程间通信的一种有限制的方式服务器
它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操做系统中断了进程正常的控制流程。此时,任何非原子操做都将被中断。若是进程定义了信号的处理函数,那么它将被执行,不然就执行默认的处理函数less
$ 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
一、替换可执行文件或修改配置文件
二、发送信号量 SIGHUP
三、拒绝新链接请求旧进程,但要保证已有链接正常
四、启动新的子进程
五、新的子进程开始 Accet
六、系统将新的请求转交新的子进程
七、旧进程处理完全部旧链接后正常结束
Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3+)
咱们借助 fvbock/endless 来实现 Golang HTTP/HTTPS
服务从新启动的零停机
endless server
监听如下几种信号量:
fork
子进程和从新启动hammerTime
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.8
,也能够考虑使用 http.Server
的 Shutdown 方法
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") }
在平常的服务中,优雅的重启(热更新)是很是重要的一环。而 Golang
在 HTTP
服务方面的热更新也有很多方案了,咱们应该根据实际应用场景挑选最合适的