<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文講懂 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ì)造成客戶端鏈接中斷(收到 RST
          2. 新請(qǐng)求打過(guò)來(lái),服務(wù)還沒(méi)重啟完畢,造成 connection refused
          3. 即使是要退出程序,直接 kill -9 仍然會(huì)讓正在處理的請(qǐng)求中斷

          很直接的感受就是:在重啟過(guò)程中,會(huì)有一段時(shí)間不能給用戶提供正常服務(wù);同時(shí)粗魯關(guān)閉服務(wù),也可能會(huì)對(duì)業(yè)務(wù)依賴的數(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 可以優(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í)候就要依賴 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è)就依賴 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)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本免费一级黄色片 | 午夜综合在线 | 美女高潮视频在线观看免费视频 | 精品成人一区二区三区 | 午夜性爱视频 |