網站首頁 編程語言 正文
為什么需要優雅關閉
什么叫優雅關閉?先說不優雅關閉,就是什么都不管,強制關閉進程,這會導致有些正在處理中的請求被強行中斷
這樣做有什么問題?
- 用戶本次請求會失敗,降低用戶體驗
- 沒有事務的數據庫操作,會產生部分成功的問題,破壞原子性
- 某些緩服務需要定期將本地緩存刷到遠程db,強制關閉會導致數據丟失
優雅關閉的核心是以下功能:
- 如何監聽退出信號
- 如何拒絕新請求
- 如何等待進行中的請求處理完畢
監控服務退出信號
在go中使用下面的代碼監聽退出信號,如果c返回,說明監聽到信號
不同的操作系統監聽不同的退出信號
c := make(chan os.Signal, 1) signals := []os.Signal{ syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, } signal.Notify(c, signals...) <-c
拒絕新請求
go在1.8后增加了shutdown方法來,我們看看它怎么實現優雅關閉:
srv.inShutdown.setTrue() lnerr := srv.closeListenersLocked() srv.closeDoneChanLocked()
- 設置inShutdown標志位
- 關閉所有的listener
- 關閉doneChan
這一段對應到http服務接收請求的流程:
for { rw, err := l.Accept() if err != nil { select { case <-srv.getDoneChan(): return ErrServerClosed // ... }
一旦關閉listener,關閉doneChan后,http服務就不會再接收新的請求,直接返回
執行關閉之前的回調
for _, f := range srv.onShutdown { go f() }
這里的回調實現得比較粗糙:
- 沒有優先級的概念,所有回調并發執行,因此需要保證回調之間沒有依賴
- 雖然回調不適合長時間運行,但Go http沒有提供機制來保證這些回調一定能執行完畢,若想做到這點需要自己處理
等待處理中的請求執行完畢
設置標識位可以拒絕新的請求,但依舊在執行的請求還在處理中,需要等這些請求執行完畢
等待處理中的請求執行完畢有兩種思路:
- 等待一段固定的時間
- 實時維護請求的計數
go選擇了兩種方式結合的模式,通過ctx設置一個最大的等待時間,同時不斷輪詢正在請求中的計數
ctx超時或者計數變為0,都會返回
timer := time.NewTimer(nextPollInterval()) defer timer.Stop() for { if srv.closeIdleConns() && srv.numListeners() == 0 { return lnerr } select { case <-ctx.Done(): return ctx.Err() case <-timer.C: timer.Reset(nextPollInterval()) } }
這里每隔一定時間檢查已有請求是否執行完畢,如果執行完畢,或者外部通過ctx設置的超時到期就會返回
檢查間隔是多少?
- 從1ms開始,每輪檢查后倍增,最大500ms
怎么判斷是否執行完畢?
- 所有的連接都關閉
- 所有的listener都關閉
服務收到監聽信號返回之前,關閉連接和listener,會被這里檢查到
實戰
func main() { // 注冊路由 http.Handle("/aaa", http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { time.Sleep(time.Second * 10) fmt.Println(111) })) server := http.Server{ Addr: "localhost:8080", Handler: http.DefaultServeMux, } close := make(chan int) go func() { quit := make(chan os.Signal) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) <-quit ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) defer cancel() err := server.Shutdown(ctx) log.Print(err) // 控制外層退出 close <- 1 }() err := server.ListenAndServe() fmt.Println(err) <-close }
該代碼做了下面的事:
- 注冊一個10s才返回的路由處理函數
- 開子協程監聽OS的退出信號,如果監聽到了開始進行優雅關閉,雖多等待30s
- 主協程調用 server.ListenAndServe(),開始監聽請求
需要注意的是,一定要在子協程中優雅關閉結束后,主協程才能退出,這里用channel控制
因為主協程發現doneChan被關閉時會馬上返回,但此時主協程開的業務處理協程還在進行中,如果主協程此時退出,無法達到優雅關閉的效果
按照以下流程測試:
- 啟動 Web 服務
- 在瀏覽器請求http://localhost:8080/aaa
- 過5秒后在控制臺按下ctrl+c
- 觀察控制臺程序是否不會立刻結束,而是在 10s 后結束
支持強制退出
既然有優雅退出,那就有強制退出,我們假設如果按下兩次ctrl+c,代表用戶希望服務強制退出:
close := make(chan int, 2) go func() { quit := make(chan os.Signal) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) <-quit go func() { <-quit os.Exit(1) }() // ... }()
做法很簡單,收到第一個退出信號后,再開一個子協程,如果再收到退出信號,就調用os.Exit退出進程
并且close channel的容量需要為2,避免當兩次退出信號過短時丟失信號
原文鏈接:https://blog.csdn.net/qq_39383767/article/details/128886199
相關推薦
- 2024-04-08 啟動spring-boot出現Error creating bean with name ‘conf
- 2022-02-01 Axure谷歌瀏覽器Chrome擴展程序下載及安裝方法
- 2023-11-17 深度學習中分布式訓練的現狀及未來
- 2022-08-16 Python利用fastapi實現上傳文件_python
- 2023-01-05 使用sqlplus連接Oracle數據庫問題_oracle
- 2022-09-13 Golang優雅保持main函數不退出的辦法_Golang
- 2023-01-05 Python中使用jpype調用Jar包中的實現方法_python
- 2023-05-20 openGauss數據庫共享存儲特性概述_數據庫其它
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支