<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Gong服務實現平滑重啟分析

      平滑重啟是指能讓我們的程序在重啟的過程不中斷服務,新老進程無縫銜接,實現零停機時間(Zero-Downtime)部署;

      平滑重啟是建立在優雅退出的基礎之上的,之前一篇文章介紹了相關實現:Golang中使用Shutdown特性對http服務進行優雅退出使用總結

      目前實現平滑重啟的主要策略有兩種:

      方案一:我們的服務如果是多機器部署,可以通過網關程序,將即將重啟服務的機器從網關下線,重啟完成后再重新上線,該方案適合多機器部署的企業級應用;

      方案二:讓我們的程序實現自啟動,重啟子進程來實現平滑重啟,核心策略是通過拷貝文件描述符實現子進程和父進程切換,適合單機器部署應用;

       

      今天我們就主要介紹方案二,讓我們的程序擁有平滑重啟的功能,相關實現參考一個開源庫:https://github.com/fvbock/endless

       

      實現原理介紹

      http 連接介紹:

      我們知道,http 服務也是基于 tcp 連接,我們通過 golang http 包源碼也能看到底層是通過監聽 tcp 連接實現的;

      func (srv *Server) ListenAndServe() error {
          if srv.shuttingDown() {
              return ErrServerClosed
          }
          addr := srv.Addr
          if addr == "" {
              addr = ":http"
          }
          ln, err := net.Listen("tcp", addr)
          if err != nil {
              return err
          }
          return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
      }

       

      復用 socket:

      當程序開啟 tcp 連接監聽時會創建一個 socket 并返回一個文件描述符 handler 給我們的程序; 

      通過拷貝文件描述符文件可以使 socket 不關閉繼續使用原有的端口,自然 http 連接也不會斷開,啟動一個相同的進程也不會出現端口被占用的問題;

      通過如下代碼進行測試:

      package main
      
      import (
          "fmt"
          "net/http"
          "context"
          "time"
          "os"
          "os/signal"
          "syscall"
          "net"
          "flag"
          "os/exec"
      )
      
      var (
          graceful = flag.Bool("grace", false, "graceful restart flag")
          procType = ""
      )
      
      func main() {
          flag.Parse()
          mux := http.NewServeMux()
          mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      
              fmt.Fprintln(w, fmt.Sprintf("Hello world! ===> %s", procType))
          })
          server := &http.Server{
              Addr:         ":8080",
              Handler:      mux,
      
          }
      
          var err error
          var listener net.Listener
          if *graceful {
              f := os.NewFile(3, "")
              listener, err = net.FileListener(f)
              procType = "fork process"
          } else {
              listener, _ = net.Listen("tcp", server.Addr)
              procType = "main process"
      
              //主程序開啟5s 后 fork 子進程
              go func() {
                  time.Sleep(5*time.Second)
                  forkSocket(listener.(*net.TCPListener))
              }()
      
          }
      
          err=server.Serve(listener.(*net.TCPListener))
      
          fmt.Println(fmt.Sprintf("proc exit %v", err))
      }
      
      
      func forkSocket(tcpListener *net.TCPListener) error {
          f, err := tcpListener.File()
          if err != nil {
              return err
          }
      
          args := []string{"-grace"}
          fmt.Println(os.Args[0], args)
          cmd := exec.Command(os.Args[0], args...)
          cmd.Stdout = os.Stdout
          cmd.Stderr = os.Stderr
          // put socket FD at the first entry
          cmd.ExtraFiles = []*os.File{f}
          return cmd.Start()
      }

      該程序啟動后,等待 5s 會自動 fork 子進程,通過 ps 命令查看如圖可以看到有兩個進程同時共存:

      然后我們可以通過瀏覽器訪問 http://127.0.0.1/ 可以看到會隨機顯示主進程或子進程的輸出;

      寫一個測試代碼進行循環請求:

      package main
      
      import (
          "net/http"
          "io/ioutil"
          "fmt"
          "sync"
      )
      
      func main(){
      
          wg:=sync.WaitGroup{}
          wg.Add(100)
          for i:=0; i<100; i++ {
              go func(index int) {
                  result:=getUrl(fmt.Sprintf("http://127.0.0.1:8080?%d", i))
                  fmt.Println(fmt.Sprintf("loop:%d %s", index, result))
                  wg.Done()
              }(i)
          }
          wg.Wait()
      }
      
      func getUrl(url string) string{
          resp, _ := http.Get(url)
          defer resp.Body.Close()
          body, _ := ioutil.ReadAll(resp.Body)
          return string(body)
      }

       

       

      能看到返回的數據也是有些是主進程有些是子進程。

       

      切換過程:

      在開啟新的進程和老進程退出的瞬間,會有一個短暫的瞬間是同時有兩個進程使用同一個文件描述符,此時這種狀態,通過http請求訪問,會隨機請求到新進程或老進程上,這樣也沒有問題,因為請求不是在新進程上就是在老進程上;當老進程結束后請求就會全部到新進程上進行處理,通過這種方式即可實現平滑重啟;

       

      綜上,我們可以將核心的實現總結如下:

      1.監聽退出信號;

      2.監聽到信號后 fork 子進程,使用相同的命令啟動程序,將文件描述符傳遞給子進程;

      3.子進程啟動后,父進程停止服務并處理正在執行的任務(或超時)退出;

      4.此時只有一個新的進程在運行,實現平滑重啟。

       

      一個完整的 demo 代碼,通過發送 USR1 信號,程序會自動創建子進程并關閉主進程,實現平滑重啟:

       

      package main
      
      import (
          "fmt"
          "net/http"
          "context"
          "os"
          "os/signal"
          "syscall"
          "net"
          "flag"
          "os/exec"
      )
      
      var (
          graceful = flag.Bool("grace", false, "graceful restart flag")
      )
      
      func main() {
          flag.Parse()
          mux := http.NewServeMux()
          mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
      
              fmt.Fprintln(w, "Hello world!")
          })
          server := &http.Server{
              Addr:         ":8080",
              Handler:      mux,
      
          }
      
          var err error
          var listener net.Listener
          if *graceful {
              f := os.NewFile(3, "")
              listener, err = net.FileListener(f)
          } else {
              listener, err = net.Listen("tcp", server.Addr)
          }
          if err != nil{
              fmt.Println(fmt.Sprintf("listener error %v", err))
              return
          }
      
          go listenSignal(context.Background(), server, listener)
      
          err=server.Serve(listener.(*net.TCPListener))
          fmt.Println(fmt.Sprintf("proc exit %v", err))
      }
      
      
      func forkSocket(tcpListener *net.TCPListener) error {
          f, err := tcpListener.File()
          if err != nil {
              return err
          }
      
          args := []string{"-grace"}
          fmt.Println(os.Args[0], args)
          cmd := exec.Command(os.Args[0], args...)
          cmd.Stdout = os.Stdout
          cmd.Stderr = os.Stderr
          // put socket FD at the first entry
          cmd.ExtraFiles = []*os.File{f}
          return cmd.Start()
      }
      
      
      
      
      func listenSignal(ctx context.Context, httpSrv *http.Server, listener net.Listener) {
          sigs := make(chan os.Signal, 1)
          signal.Notify(sigs, syscall.USR1)
      
      
          select {
          case <-sigs:
              forkSocket(listener.(*net.TCPListener))
              httpSrv.Shutdown(ctx)
              fmt.Println("http shutdown")
          }
      }

      使用 apache 的 ab 壓測工具進行驗證一下,執行 ab -c 50 -t 20 http://127.0.0.1:8080/ 持續 50 的并發 20s,在壓測的期間向程序運行的pid發送 USR1 信號,可以看到壓測結果,沒有失敗的請求,由此可知,該方案實現平滑重啟是木有問題的。

       

      最后給大家安利一個 Web 開發框架,該框架已經將平滑重啟進行的封裝,開箱即用,快速構建一個帶平滑重啟的 Web 服務。

      框架源碼:https://gitee.com/zhucheer/orange

      文檔:https://www.kancloud.cn/chase688/orange_framework/1448035

      posted @ 2020-02-27 00:04  qīqíqǐqì  閱讀(1620)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 中文字幕在线视频不卡一区二区| 最新日韩精品中文字幕| 中文字幕成人精品久久不卡| 国产精品va无码一区二区| 日韩欧美不卡一卡二卡3卡四卡2021免费 | 久久久久影院色老大2020| 精品无码成人片一区二区| 最新中文字幕国产精品| 日韩中文字幕综合第二页| 激情欧美日韩一区二区| 久久夜色精品国产亚av| 717午夜伦伦电影理论片| 久久精品久久电影免费理论片| 人与禽交av在线播放| 国产精品18久久久久久麻辣| 久久国产乱子精品免费女| 少妇伦子伦精品无吗| 最近免费中文字幕mv在线视频3| 国产免费又黄又爽又色毛| 国产精品亚洲五月天高清| 冕宁县| 久热久视频免费在线观看| 中文字幕人妻日韩精品| 精品日韩亚洲AV无码| 久久久久影院色老大2020| 日本熟妇色xxxxx日本免费看 | 大地资源网第二页免费观看| 国产精品色哟哟成人av| 余干县| 人妻少妇无码精品专区| 国产精品福利在线观看无码卡一| 久久精品国产亚洲av品| 国内精品久久久久影院不卡| 亚洲最大天堂在线看视频| 性夜夜春夜夜爽夜夜免费视频| 伦伦影院午夜理论片| 欧美XXXX黑人又粗又长| 成人免费无遮挡在线播放| 国内揄拍国内精品人妻久久| xxxx丰满少妇高潮| 国产欧美国日产高清|