1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文講懂 Go 服務(wù)的優(yōu)雅重啟和更新

        共 5094字,需瀏覽 11分鐘

         ·

        2021-06-24 01:00

        在服務(wù)端程序更新或重啟時(shí),如果我們直接 kill -9 殺掉舊進(jìn)程并啟動(dòng)新進(jìn)程,會(huì)有以下幾個(gè)問(wèn)題:

        1. 舊的請(qǐng)求未處理完,如果服務(wù)端進(jìn)程直接退出,會(huì)造成客戶(hù)端鏈接中斷(收到 RST
        2. 新請(qǐng)求打過(guò)來(lái),服務(wù)還沒(méi)重啟完畢,造成 connection refused
        3. 即使是要退出程序,直接 kill -9 仍然會(huì)讓正在處理的請(qǐng)求中斷

        很直接的感受就是:在重啟過(guò)程中,會(huì)有一段時(shí)間不能給用戶(hù)提供正常服務(wù);同時(shí)粗魯關(guān)閉服務(wù),也可能會(huì)對(duì)業(yè)務(wù)依賴(lài)的數(shù)據(jù)庫(kù)等狀態(tài)服務(wù)造成污染。

        所以我們服務(wù)重啟或者是重新發(fā)布過(guò)程中,要做到新舊服務(wù)無(wú)縫切換,同時(shí)可以保障變更服務(wù) 零宕機(jī)時(shí)間!

        作為一個(gè)微服務(wù)框架,那 go-zero 是怎么幫開(kāi)發(fā)者做到優(yōu)雅退出的呢?下面我們一起看看。

        優(yōu)雅退出

        在實(shí)現(xiàn)優(yōu)雅重啟之前首先需要解決的一個(gè)問(wèn)題是 如何優(yōu)雅退出

        對(duì) http 服務(wù)來(lái)說(shuō),一般的思路就是關(guān)閉對(duì) fdlisten , 確保不會(huì)有新的請(qǐng)求進(jìn)來(lái)的情況下處理完已經(jīng)進(jìn)入的請(qǐng)求, 然后退出。

        go 原生中 http 中提供了 server.ShutDown(),先來(lái)看看它是怎么實(shí)現(xiàn)的:

        1. 設(shè)置 inShutdown 標(biāo)志
        2. 關(guān)閉 listeners 保證不會(huì)有新請(qǐng)求進(jìn)來(lái)
        3. 等待所有活躍鏈接變成空閑狀態(tài)
        4. 退出函數(shù),結(jié)束

        分別來(lái)解釋一下這幾個(gè)步驟的含義:

        inShutdown

        func (srv *Server) ListenAndServe() error {
            if srv.shuttingDown() {
                return ErrServerClosed
            }
            ....
            // 實(shí)際監(jiān)聽(tīng)端口;生成一個(gè) listener
            ln, err := net.Listen("tcp", addr)
            if err != nil {
                return err
            }
            // 進(jìn)行實(shí)際邏輯處理,并將該 listener 注入
            return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
        }

        func (s *Server) shuttingDown() bool {
          return atomic.LoadInt32(&s.inShutdown) != 0
        }

        ListenAndServe 是http啟動(dòng)服務(wù)器的必經(jīng)函數(shù),里面的第一句就是判斷 Server 是否被關(guān)閉了。

        inShutdown 就是一個(gè)原子變量,非0表示被關(guān)閉。

        listeners

        func (srv *Server) Serve(l net.Listener) error {
            ...
            // 將注入的 listener 加入內(nèi)部的 map 中
            // 方便后續(xù)控制從該 listener 鏈接到的請(qǐng)求
            if !srv.trackListener(&l, true) {
                return ErrServerClosed
            }
            defer srv.trackListener(&l, false)
           ...
        }

        Serve 中注冊(cè)到內(nèi)部 listeners maplistener,在 ShutDown 中就可以直接從 listeners 中獲取到,然后執(zhí)行 listener.Close(),TCP四次揮手后,新的請(qǐng)求就不會(huì)進(jìn)入了。

        closeIdleConns

        簡(jiǎn)單來(lái)說(shuō)就是:將目前 Server 中記錄的活躍鏈接變成變成空閑狀態(tài),返回。

        關(guān)閉

        func (srv *Server) Serve(l net.Listener) error {
          ...
          for {
           rw, err := l.Accept()
            // 此時(shí) accept 會(huì)發(fā)生錯(cuò)誤,因?yàn)榍懊嬉呀?jīng)將 listener close了
           if err != nil {
            select {
            // 又是一個(gè)標(biāo)志:doneChan
            case <-srv.getDoneChan():
             return ErrServerClosed
            default:
            }
            }
          }
        }

        其中 getDoneChan 中已經(jīng)在前面關(guān)閉 listener  時(shí),對(duì) doneChan 這個(gè)channel中push。

        總結(jié)一下:Shutdown 可以?xún)?yōu)雅的終止服務(wù),期間不會(huì)中斷已經(jīng)活躍的鏈接。

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

        服務(wù)中斷

        這個(gè)時(shí)候就要依賴(lài) OS 本身提供的 signal。對(duì)應(yīng) go 原生來(lái)說(shuō),signalNotify 提供系統(tǒng)信號(hào)通知的能力。

        https://github.com/tal-tech/go-zero/blob/master/core/proc/signals.go

        func init() {
         go func() {
           var profiler Stopper
            
          signals := make(chan os.Signal, 1)
           signal.Notify(signals, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGTERM)

           for {
            v := <-signals
            switch v {
            case syscall.SIGUSR1:
             dumpGoroutines()
            case syscall.SIGUSR2:
             if profiler == nil {
              profiler = StartProfile()
             } else {
              profiler.Stop()
              profiler = nil
             }
           case syscall.SIGTERM:
                // 正在執(zhí)行優(yōu)雅關(guān)閉的地方
             gracefulStop(signals)
            default:
             logx.Error("Got unregistered signal:", v)
            }
           }
          }()
        }
        • SIGUSR1 -> 將 goroutine 狀況,dump下來(lái),這個(gè)在做錯(cuò)誤分析時(shí)還挺有用的

        • SIGUSR2 -> 開(kāi)啟/關(guān)閉所有指標(biāo)監(jiān)控,自行控制 profiling 時(shí)長(zhǎng)

        • SIGTERM -> 真正開(kāi)啟 gracefulStop,優(yōu)雅關(guān)閉

        gracefulStop 的流程如下:

        1. 取消監(jiān)聽(tīng)信號(hào),畢竟要退出了,不需要重復(fù)監(jiān)聽(tīng)了
        2. wrap up,關(guān)閉目前服務(wù)請(qǐng)求,以及資源
        3. time.Sleep() ,等待資源處理完成,以后關(guān)閉完成
        4. shutdown ,通知退出
        5. 如果主goroutine還沒(méi)有退出,則主動(dòng)發(fā)送 SIGKILL 退出進(jìn)程

        這樣,服務(wù)不再接受新的請(qǐng)求,服務(wù)活躍的請(qǐng)求等待處理完成,同時(shí)也等待資源關(guān)閉(數(shù)據(jù)庫(kù)連接等),如有超時(shí),強(qiáng)制退出。

        整體流程

        我們目前 go 程序都是在 docker 容器中運(yùn)行,所以在服務(wù)發(fā)布過(guò)程中,k8s 會(huì)向容器發(fā)送一個(gè) SIGTERM 信號(hào),然后容器中程序接收到信號(hào),開(kāi)始執(zhí)行 ShutDown

        到這里,整個(gè)優(yōu)雅關(guān)閉的流程就梳理完畢了。

        但是還有平滑重啟,這個(gè)就依賴(lài) k8s 了,基本流程如下:

        • old pod 未退出之前,先啟動(dòng) new pod
        • old pod 繼續(xù)處理完已經(jīng)接受的請(qǐng)求,并且不再接受新請(qǐng)求
        • new pod接受并處理新請(qǐng)求的方式
        • old pod 退出

        這樣整個(gè)服務(wù)重啟就算是成功了,如果 new pod 沒(méi)有啟動(dòng)成功,old pod 也可以提供服務(wù),不會(huì)對(duì)目前線上的服務(wù)造成影響。



        推薦閱讀


        福利

        我為大家整理了一份從入門(mén)到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門(mén)看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬(wàn) Gopher 交流學(xué)習(xí)。

        瀏覽 50
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲AV无码五区六区七区八区 | 国产男女激情视频 | 九色porny国产 | 97操 | 中文字幕一二区 | 古装三级做爰在线观看 | 寡妇高潮一级毛片免费看老牛影视 | 国产疯狂做受xxxx高潮 | 美女裸18禁 | 欧美另类old老太妇 |