溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Golang怎么優(yōu)雅的終止一個(gè)服務(wù)

發(fā)布時(shí)間:2022-03-21 15:37:33 來源:億速云 閱讀:276 作者:iii 欄目:開發(fā)技術(shù)

今天小編給大家分享一下Golang怎么優(yōu)雅的終止一個(gè)服務(wù)的相關(guān)知識點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

前言

采用常規(guī)方式啟動(dòng)一個(gè) Golang http 服務(wù)時(shí),若服務(wù)被意外終止或中斷,即未等待服務(wù)對現(xiàn)有請求連接處理并正常返回且亦未對服務(wù)停止前作一些必要的處理工作,這樣即會(huì)造成服務(wù)硬終止。這種方式不是很優(yōu)雅。

參看如下代碼,該 http 服務(wù)請求路徑為根路徑,請求該路徑,其會(huì)在 2s 后返回 hello。

var addr = flag.String("server addr", ":8080", "server address")

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(2 * time.Second)
        fmt.Fprintln(w, "hello")
    })
    http.ListenAndServe(*addr, nil)
}

若服務(wù)啟動(dòng)后,請求http://localhost:8080/,然后使用 Ctrl+C 立即中斷服務(wù),服務(wù)即會(huì)立即退出(exit status 2),請求未正常返回(ERR_CONNECTION_REFUSED),連接即馬上斷了。

接下來介紹使用 http.Server 的 Shutdown 方法結(jié)合 signal.Notify 來優(yōu)雅的終止服務(wù)。

1 Shutdown 方法

Golang http.Server 結(jié)構(gòu)體有一個(gè)終止服務(wù)的方法 Shutdown,其 go doc 如下。

func (srv *Server) Shutdown(ctx context.Context) error
    Shutdown gracefully shuts down the server without interrupting any active
    connections. Shutdown works by first closing all open listeners, then
    closing all idle connections, and then waiting indefinitely for connections
    to return to idle and then shut down. If the provided context expires before
    the shutdown is complete, Shutdown returns the context's error, otherwise it
    returns any error returned from closing the Server's underlying Listener(s).

    When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS
    immediately return ErrServerClosed. Make sure the program doesn't exit and
    waits instead for Shutdown to return.

    Shutdown does not attempt to close nor wait for hijacked connections such as
    WebSockets. The caller of Shutdown should separately notify such long-lived
    connections of shutdown and wait for them to close, if desired. See
    RegisterOnShutdown for a way to register shutdown notification functions.

    Once Shutdown has been called on a server, it may not be reused; future
    calls to methods such as Serve will return ErrServerClosed.

由文檔可知:

使用 Shutdown 可以優(yōu)雅的終止服務(wù),其不會(huì)中斷活躍連接。

其工作過程為:首先關(guān)閉所有開啟的監(jiān)聽器,然后關(guān)閉所有閑置連接,最后等待活躍的連接均閑置了才終止服務(wù)。

若傳入的 context 在服務(wù)完成終止前已超時(shí),則 Shutdown 方法返回 context 的錯(cuò)誤,否則返回任何由關(guān)閉服務(wù)監(jiān)聽器所引起的錯(cuò)誤。

當(dāng) Shutdown 方法被調(diào)用時(shí),Serve、ListenAndServe 及 ListenAndServeTLS 方法會(huì)立刻返回 ErrServerClosed 錯(cuò)誤。請確保 Shutdown 未返回時(shí),勿退出程序。

對諸如 WebSocket 等的長連接,Shutdown 不會(huì)嘗試關(guān)閉也不會(huì)等待這些連接。若需要,需調(diào)用者分開額外處理(諸如通知諸長連接或等待它們關(guān)閉,使用 RegisterOnShutdown 注冊終止通知函數(shù))。

一旦對 server 調(diào)用了 Shutdown,其即不可再使用了(會(huì)報(bào) ErrServerClosed 錯(cuò)誤)。

有了 Shutdown 方法,我們知道在服務(wù)終止前,調(diào)用該方法即可等待活躍連接正常返回,然后優(yōu)雅的關(guān)閉。

但服務(wù)啟動(dòng)后的某一時(shí)刻,程序如何知道服務(wù)被中斷了呢?服務(wù)被中斷時(shí)如何通知程序,然后調(diào)用 Shutdown 作處理呢?接下來看一下系統(tǒng)信號通知函數(shù)的作用。

2 signal.Notify 函數(shù)

signal 包的 Notify 函數(shù)提供系統(tǒng)信號通知的能力,其 go doc 如下。

func Notify(c chan<- os.Signal, sig ...os.Signal)
    Notify causes package signal to relay incoming signals to c. If no signals
    are provided, all incoming signals will be relayed to c. Otherwise, just the
    provided signals will.

    Package signal will not block sending to c: the caller must ensure that c
    has sufficient buffer space to keep up with the expected signal rate. For a
    channel used for notification of just one signal value, a buffer of size 1
    is sufficient.

    It is allowed to call Notify multiple times with the same channel: each call
    expands the set of signals sent to that channel. The only way to remove
    signals from the set is to call Stop.

    It is allowed to call Notify multiple times with different channels and the
    same signals: each channel receives copies of incoming signals
    independently.

由文檔可知:

參數(shù) c 是調(diào)用者的信號接收通道,Notify 可將進(jìn)入的信號轉(zhuǎn)到 c。sig 參數(shù)為需要轉(zhuǎn)發(fā)的信號類型,若不指定,所有進(jìn)入的信號都將會(huì)轉(zhuǎn)到 c。

信號不會(huì)阻塞式的發(fā)給 c:調(diào)用者需確保 c 有足夠的緩沖空間,以應(yīng)對指定信號的高頻發(fā)送。對于用于通知僅一個(gè)信號值的通道,緩沖大小為 1 即可。

同一個(gè)通道可以調(diào)用 Notify 多次:每個(gè)調(diào)用擴(kuò)展了發(fā)送至該通道的信號集合。僅可調(diào)用 Stop 來從信號集合移除信號。

允許不同的通道使用同樣的信號參數(shù)調(diào)用 Notify 多次:每個(gè)通道獨(dú)立的接收進(jìn)入信號的副本。

綜上,有了 signal.Notify,傳入一個(gè) chan 并指定中斷參數(shù),這樣當(dāng)系統(tǒng)中斷時(shí),即可接收到信號。

參看如下代碼,當(dāng)使用 Ctrl+C 時(shí),c 會(huì)接收到中斷信號,程序會(huì)在打印“program interrupted”語句后退出。

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt)
    <-c
    log.Fatal("program interrupted")
}
$ go run main.go

Ctrl+C

2019/06/11 17:59:11 program interrupted
exit status 1

3 Server 優(yōu)雅的終止

接下來我們使用如上 signal.Notify 結(jié)合 http.Server 的 Shutdown 方法實(shí)現(xiàn)服務(wù)優(yōu)雅的終止。

如下代碼,Handler 與文章開始時(shí)的處理邏輯一樣,其會(huì)在2s后返回 hello。

創(chuàng)建一個(gè) http.Server 實(shí)例,指定端口與 Handler。

聲明一個(gè) processed chan,其用來保證服務(wù)優(yōu)雅的終止后再退出主 goroutine。

新啟一個(gè) goroutine,其會(huì)監(jiān)聽 os.Interrupt 信號,一旦服務(wù)被中斷即調(diào)用服務(wù)的 Shutdown 方法,確保活躍連接的正常返回(本代碼使用的 Context 超時(shí)時(shí)間為 3s,大于服務(wù) Handler 的處理時(shí)間,所以不會(huì)超時(shí))。

處理完成后,關(guān)閉 processed 通道,最后主 goroutine 退出。

代碼同時(shí)托管在 GitHub,歡迎關(guān)注(github.com/olzhy/go-excercises)。

var addr = flag.String("server addr", ":8080", "server address")

func main() {
    // handler
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(2 * time.Second)
        fmt.Fprintln(w, "hello")
    })

    // server
    srv := http.Server{
        Addr:    *addr,
        Handler: handler,
    }

    // make sure idle connections returned
    processed := make(chan struct{})
    go func() {
        c := make(chan os.Signal, 1)
        signal.Notify(c, os.Interrupt)
        <-c

        ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
        defer cancel()
        if err := srv.Shutdown(ctx); nil != err {
            log.Fatalf("server shutdown failed, err: %v\n", err)
        }
        log.Println("server gracefully shutdown")

        close(processed)
    }()

    // serve
    err := srv.ListenAndServe()
    if http.ErrServerClosed != err {
        log.Fatalf("server not gracefully shutdown, err :%v\n", err)
    }

    // waiting for goroutine above processed
    <-processed
}

以上就是“Golang怎么優(yōu)雅的終止一個(gè)服務(wù)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI