網頁

2022/12/11

Golang HTTP Server.Shutdown graceful shutdown

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.Notifyos.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")
}

github



測試

上面範例建了一個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


沒有留言:

張貼留言