在 Go 1.16 的更新中,signal包增加了一個函數 NotifyContext,
這讓我們優雅的重啟服務(Graceful Restart)可以寫的更加優雅。
一個服務想要優雅的重啟主要包含兩個方面:
- 退出的舊服務需要
Graceful Shutdown,不強制殺進程,不泄漏系統資源。 - 在一個集群內輪流重啟服務實例,保證服務不中斷。
第二個問題跟部署方式相關,改天專門寫一篇討論,今天我們主要談怎么樣優雅的退出。
首先在代碼里,用了外部資源,一定要使用defer去調用Close()方法關閉。
然后我們就要攔截系統的中斷信號,保證程序收到中斷信號之后,主動有序退出,這樣所有的 defer 才會被執行。
在以前,大概是這么寫:
func everLoop(ctx context.Context) {
LOOP:
for {
select {
case <-ctx.Done():
// 收到信號退出無限循環
break LOOP
default:
// 用一個 sleep 模擬業務邏輯
time.Sleep(time.Second * 10)
}
}
}
func main() {
// 建立一個可以手動取消的 Context
ctx, cancel := context.WithCancel(context.Background())
// 監控系統信號,這里只監控了 SIGINT(Ctrl+c),SIGTERM
// 在 systemd 和 docker 中,都是先發 SIGTERM,過一段時間沒退出再發 SIGKILL
// 所以這里沒捕獲 SIGKILL
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sig
cancel()
}()
// 開始無限循環,收到信號就會退出
everLoop(ctx)
fmt.Println("graceful shuwdown")
}
現在有了新的函數,這一段變得更簡單了:
func main() {
// 監控系統信號和創建 Context 現在一步搞定
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
// 在收到信號的時候,會自動觸發 ctx 的 Done ,這個 stop 是不再捕獲注冊的信號的意思,算是一種釋放資源。
defer stop()
// 開始無限循環,收到信號就會退出
everLoop(ctx)
fmt.Println("graceful shuwdown")
}
感謝 Golang ,當年用別的語言需要寫一大堆代碼的功能,現在幾行就可以輕松實現了。
讓它成為你服務程序的標配吧。
最后,我是寫最新的項目LetServerRun的時候,發現這種最新的寫法的。
LetServerRun 可以讓你把微信公眾號當作隨身的 Terminal 控制你的服務端。
在它的 Agent 的 main 函數中就有上述用法的示例,
歡迎參考。
Java伴侶
