文章目录
- 知识点
- 本节目标
- 何谓优雅
- ctrl+c
- 信号
- 修改流程
- 实现优雅重启
- endless
- 安装
- 编写
- 验证
- 编译
- 执行
- 唤醒
- 问题
续接 上一节
知识点
- 信号量的了解
- 应用热更新
本节目标
在前文中,我们在配置玩之后直接使用 ctrl+c
来进行进程的结束,我们将了解 ctrl+c
的过程中到底进行了什么
简单讲述 ctrl+c
背后的信号 以及如何在gin
中优雅的重启服务,也就是对HTTP服务进行热更新
何谓优雅
- 不关闭现有连接(正在运行中的程序)
- 新的进程启动并替代旧进程
- 新的进程接管新的连接
- 连接要随时相应用户的请求,当用户仍在请求旧进程时要保持连接,新用户应请求新进程,不可以出现拒绝请求的情况
ctrl+c
内核在某些情况下发送信号,比如在进程往一个已经关闭的管道写数据时会产生
SIGPIPE
信号
我们在执行ctrl+c
关闭gin
服务端时,会强制结束进程,导致正在访问的用户等出现问题
信号
信号是 Unix
、类 Unix
以及其他POSIX
兼容的操作系统中进程间通讯的一种有限制的方式
它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程。此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数
修改流程
- 替换可执行文件或修改配置文件
- 发送信号量
- 拒绝新连接请求旧进程,但要保证已有连接进程
- 去启动新的子进程
- 新的子进程开始accept
- 系统将新的请求转交新的子进程
- 旧进程处理玩所有旧连接后正常结束
实现优雅重启
我们借助 fvbock/endless
来实现 Golang HTTP/HTTPS
服务重新启动的零停机
endless
借助 fvbock/endless 来实现 Golang HTTP/HTTPS
服务重新启动的零停机
endless server
监听以下几种信号量:
syscall.SIGHUP
:触发 fork 子进程和重新启动syscall.SIGUSR1/syscall.SIGTSTP
:被监听,但不会触发任何动作syscall.SIGUSR2
:触发 hammerTimesyscall.SIGINT/syscall.SIGTERM
:触发服务器关闭(会完成正在运行的请求)
安装
go get -u github.com/fvbock/endless
编写
打开 gin-blog
的 main.go
文件,修改文件:
...
func main() {/* 之前的版本//将原来创建的默认router和建立对应的handler绑定router := routers.InitRouter()//创建一个http服务器,将前面的router绑定为这里的处理器s := &http.Server{Addr: fmt.Sprintf(":%d", setting.HTTPPort),Handler: router,ReadTimeout: setting.ReadTimeout,WriteTimeout: setting.WriteTimeout,MaxHeaderBytes: 1 << 20,}//启动服务器s.ListenAndServe()*///当前版本,建议对比源代码进行理解//进行配置的导入,在setting包中进行设置endless.DefaultReadTimeOut = setting.ReadTimeoutendless.DefaultWriteTimeOut = setting.WriteTimeoutendless.DefaultMaxHeaderBytes = 1 << 20endPoint := fmt.Sprintf(":%d", setting.HTTPPort)//newsever返回一个初始化的endlessServer对象server := endless.NewServer(endPoint, routers.InitRouter())//输出当前进程的PIDserver.BeforeBegin = func(add string) {log.Printf("Actual pid is %d", syscall.Getpid())}//启动服务err := server.ListenAndServe()if err != nil {log.Printf("Server err: %v", err)}}
验证
编译
$ go build main.go
执行
$ ./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 --> github.com/kingsill/gin-example/routers/api.GetAuth (3 handlers)
[GIN-debug] GET /api/v1/tags --> github.com/kingsill/gin-example/routers/api/v1.GetTags (4 handlers)
[GIN-debug] POST /api/v1/tags --> github.com/kingsill/gin-example/routers/api/v1.AddTag (4 handlers)
[GIN-debug] PUT /api/v1/tags/:id --> github.com/kingsill/gin-example/routers/api/v1.EditTag (4 handlers)
[GIN-debug] DELETE /api/v1/tags/:id --> github.com/kingsill/gin-example/routers/api/v1.DeleteTag (4 handlers)
[GIN-debug] GET /api/v1/articles --> github.com/kingsill/gin-example/routers/api/v1.GetArticles (4 handlers)
[GIN-debug] GET /api/v1/articles/:id --> github.com/kingsill/gin-example/routers/api/v1.GetArticle (4 handlers)
[GIN-debug] POST /api/v1/articles --> github.com/kingsill/gin-example/routers/api/v1.AddArticle (4 handlers)
[GIN-debug] PUT /api/v1/articles/:id --> github.com/kingsill/gin-example/routers/api/v1.EditArticle (4 handlers)
[GIN-debug] DELETE /api/v1/articles/:id --> github.com/kingsill/gin-example/routers/api/v1.DeleteArticle (4 handlers)
2024/03/04 20:08:31 Actual pid is 593
可以看到我们当前的进程pid
为593
,在另一个终端执行kill -1 48601
2024/03/04 20:09:21 593 Received SIGHUP. forking.
[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 --> github.com/kingsill/gin-example/routers/api.GetAuth (3 handlers)
[GIN-debug] GET /api/v1/tags --> github.com/kingsill/gin-example/routers/api/v1.GetTags (4 handlers)
[GIN-debug] POST /api/v1/tags --> github.com/kingsill/gin-example/routers/api/v1.AddTag (4 handlers)
[GIN-debug] PUT /api/v1/tags/:id --> github.com/kingsill/gin-example/routers/api/v1.EditTag (4 handlers)
[GIN-debug] DELETE /api/v1/tags/:id --> github.com/kingsill/gin-example/routers/api/v1.DeleteTag (4 handlers)
[GIN-debug] GET /api/v1/articles --> github.com/kingsill/gin-example/routers/api/v1.GetArticles (4 handlers)
[GIN-debug] GET /api/v1/articles/:id --> github.com/kingsill/gin-example/routers/api/v1.GetArticle (4 handlers)
[GIN-debug] POST /api/v1/articles --> github.com/kingsill/gin-example/routers/api/v1.AddArticle (4 handlers)
[GIN-debug] PUT /api/v1/articles/:id --> github.com/kingsill/gin-example/routers/api/v1.EditArticle (4 handlers)
[GIN-debug] DELETE /api/v1/articles/:id --> github.com/kingsill/gin-example/routers/api/v1.DeleteArticle (4 handlers)
2024/03/04 20:09:21 Actual pid is 699
2024/03/04 20:09:21 593 Received SIGTERM.
2024/03/04 20:09:21 593 Waiting for connections to finish...
2024/03/04 20:09:21 593 Serve() returning...
2024/03/04 20:09:21 Server err: accept tcp [::]:8000: use of closed network connection
可以看到该命令已经挂起,并且fork
了新的进程pid为699
唤醒
这时候在 postman
上再次访问我们的接口
╭─ │ ~/gin-example │ main ?1 ········································· ✔ │ 50s │ 08:09:21 PM
╰─
(/home/wang2/gin-example/models/article.go:45)
[2024-03-04 20:09:45] [0.67ms] SELECT * FROM `blog_article` LIMIT 10 OFFSET 0
[0 rows affected or returned ]
在postman
继续访问时,当前终端从命令行重新进入程序的执行过程中
这就完成了一次正向的流转了
你想想,每次更新发布、或者修改配置文件等,只需要给该进程发送SIGTERM
信号,而不需要强制结束应用,是多么便捷又安全的事!
问题
endless
热更新是采取创建子进程后,将原进程退出的方式,这点不符合守护进程的要求