通常应用在进行发布,重启或者服务器迁移时都会让应用进程关闭,我们更希望在可预知的进程关闭时,让正常处理的任务尽量处理完成后再关闭,比如数据库操作,数据同步等等,让进程关闭重启更加的平滑优雅;
Orange 框架已封装了优雅退出相关处理逻辑,按照如下方式即可实现优雅退出。
### 实现说明
- http服务基于 http 的 shutdown 方法实现;
- 自定义业务逻辑基于 `signal.Notify` 信号监听,等待处理来实现;
### 快速开始
- 退出后,等待处理完成
~~~
// 通过该方法添加相关处理逻辑, 进程退出后会等待 `ExitWaitFunDo` 中的逻辑处理完成后再退出;
app.ExitWaitFunDo(func() {
// run something
})
~~~
- 进程退出后置操作
~~~
// 通过该方法可以在进程退出后调用到 `AppDefer` 中的方法
app.AppDefer(func() {
// run when app shutdown
})
~~~
### 防止僵尸进程
如果在 `ExitWaitFunDo` 和 `AppDefer` 中定义了死循环或长时间的任务,会导致进程不能正常退出或重启形成僵尸进程,为了防止此类情况,框架从底层设计上会防止该情况,进程监听到退出信号后,等待配置的指定超时时间还没处理完成进程也会强制退出;
~~~
[app]
maxWaitSecond=120 # 最大等待时间
~~~
### 捕获退出信号
如上述等待退出相关方法无法满足特定需求时,可以通过获取退出信号的方法完成自定义需求;
Orange 框架通过 golang 系统包 `signal.Notify` 方法获取退出信号,如应用中重复使用该方法会导致框架获取退出信号异常,因此框架对退出信号进行了封装,通过框架中的方法去 `app.ListenStop` 获取退出信号;
退出信号通过监听方法调用时传入的 channel 类型来传递,通过一个新的协程 `select` 方法来获取信号,具体代码如下。
~~~
// 监听退出信号
stopSig := make( chan app.StopSignal, 1)
go app.ListenStop(stopSig)
// 当应用退出时,会收到一个信号
go func() {
select {
case <-stopSig:
fmt.Println("get stopSign ")
}
}()
~~~
### 使用建议
- 不建议在控制器中使用 `AppDefer` 和 `ListenStop` 方法,因为调用 http 服务时每次请求都是一个新的 `goroutine` 每次请求都会新增一个重复的后置操作和监听信号;
- `AppDefer` 和 `ExitWaitFunDo` 中的处理时长应当是可预知的,避免因超时原因导致中断正常的处理逻辑。