Go 1.8開始提供的http.Server.Shutdown
可優雅地關閉server,稱為graceful shutdown。
所謂graceful shutdown是指等待進行中的連線請求都處理結束後才關閉連線,而非突然直接關閉連線可能導致請求未被完全處理造成資料丟失或不一致的問題。
http.Server.Shutdown
不會直接關閉進行中的連線,是先關閉全部的HTTP listener避免新的請求連線,接著關閉閒置的連線,最後等全部的連線都成為閒置狀態後才會關閉,而關閉後的server將無法再被使用。
不過http.Server.Shutdown
並不處理WebSocket連線的關閉,對於這類長時間的連線關閉必須另行處理。
Graceful shutdown範例
http.Server.Shutdown
的API文件有提供graceful shutdown的程式碼範例,以下做了一點微調。
範例環境:
- Go 1.19
在ListenAndServe
結束前透過另開一條goroutine來監聽中斷訊號及呼叫Server.Shutdown
進行graceful shutdown等待所有進行中的連線結束並關閉後才繼續server的關閉。
Channel idleConnsClosed
作用為接收http.Server.Shutdown
完成後的關閉訊息,在收到close(idleConnsClosed)
關閉以前會阻塞main goroutine的結束。
Channel sigint
作用為接收signal.Notify
的os.Interrupt
中斷訊號,在收到中斷訊號以前會阻塞goroutine往下執行Server.Shutdown
。
當系統發出中斷訊號(e.g.按Ctrl + C)時,sigint
會收到中斷訊號並解除goroutine的阻塞,接著呼叫http.Server.Shutdown
進行graceful shutdown,待所有進行的連線都處理完畢並關閉後呼叫close(idleConnsClosed)
關閉channel idleConnsClosed
並解除main goroutine的阻塞。
http.Server.Shutdown
執行後ListenAndServe
會回傳ErrServerClosed
。
main.go
package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("process request")
time.Sleep(time.Second * 10) // delay 10 seconds to simulate unfinished process
fmt.Fprint(w, "hello")
})
srv := &http.Server{
Addr: ":8080",
}
idleConnsClosed := make(chan struct{}) // block channal to make sure all idle connections closed before server closed
go func() {
sigint := make(chan os.Signal, 1) // channel to receive interrupt signal
signal.Notify(sigint, os.Interrupt) // receive interrupt signal to `sigint` channel
<-sigint // goroutine blocked here until receving interrup signal
fmt.Println("\nreceived an interrupt siginal")
err := srv.Shutdown(context.Background()) // graceful shutdown, wait all active connections become idle then close them
if err != nil {
fmt.Printf("graceful shutdown error=%v", err)
}
fmt.Println("idle connections closed")
close(idleConnsClosed)
}()
err := srv.ListenAndServe()
if err != http.ErrServerClosed {
panic(err)
}
<-idleConnsClosed // main goroutine blocked here until 'idleConnsClosed' closed.
fmt.Println("server closed")
}
測試
上面範例建了一個GET|/hello
的handler,其中以time.Sleep(time.Second * 10)
延遲10秒模擬比較耗時的請求連線。
啟動專案並發送請求給GET|/hello
,在得到回應以前按Ctrl + C中斷程序server不會立即停止,而是等到請求處理完回應後才關閉server,此即為graceful shutdown的效果。
操作印出訊息如下。
process /hello request
^C
received an interrupt siginal
idle connections closed
server closed
沒有留言:
張貼留言