a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > Golang > 正文

Golang學習之平滑重啟

2020-04-01 18:54:24
字體:
來源:轉載
供稿:網友

在上一篇博客介紹TOML配置的時候,講到了通過信號通知重載配置。我們在這一篇中介紹下如何的平滑重啟server。

與重載配置相同的是我們也需要通過信號來通知server重啟,但關鍵在于平滑重啟,如果只是簡單的重啟,只需要kill掉,然后再拉起即可。平滑重啟意味著server升級的時候可以不用停止業務。

我們先來看下Github上有沒有相應的庫解決這個問題,然后找到了如下三個庫:

  • facebookgo/grace - Graceful restart & zero downtime deploy for Go servers.
  • fvbock/endless - Zero downtime restarts for go servers (Drop in replacement for http.ListenAndServe)
  • jpillora/overseer - Monitorable, gracefully restarting, self-upgrading binaries in Go (golang)

我們分別來學習一下,下面只講解http server的重啟。

使用方式

我們來分別使用這三個庫來做平滑重啟的事情,之后來對比其優缺點。

這三個庫的官方都給了相應的例子,例子如下:

但三個庫官方的例子不太一致,我們來統一一下:

我們參考官方的例子分別來寫下用來對比的例子:

grace

package mainimport (  "time"  "net/http"  "github.com/facebookgo/grace/gracehttp")func main() {  gracehttp.Serve(    &http.Server{Addr: ":5001", Handler: newGraceHandler()},    &http.Server{Addr: ":5002", Handler: newGraceHandler()},  )}func newGraceHandler() http.Handler {  mux := http.NewServeMux()  mux.HandleFunc("/sleep", func(w http.ResponseWriter, r *http.Request) {    duration, err := time.ParseDuration(r.FormValue("duration"))    if err != nil {      http.Error(w, err.Error(), 400)      return    }    time.Sleep(duration)    w.Write([]byte("Hello World"))  })  return mux}

endless

package mainimport (  "log"  "net/http"  "os"  "sync"  "time"  "github.com/fvbock/endless"  "github.com/gorilla/mux")func handler(w http.ResponseWriter, r *http.Request) {  duration, err := time.ParseDuration(r.FormValue("duration"))  if err != nil {    http.Error(w, err.Error(), 400)    return  }  time.Sleep(duration)  w.Write([]byte("Hello World"))}func main() {  mux1 := mux.NewRouter()  mux1.HandleFunc("/sleep", handler)  w := sync.WaitGroup{}  w.Add(2)  go func() {    err := endless.ListenAndServe(":5003", mux1)    if err != nil {      log.Println(err)    }    log.Println("Server on 5003 stopped")    w.Done()  }()  go func() {    err := endless.ListenAndServe(":5004", mux1)    if err != nil {      log.Println(err)    }    log.Println("Server on 5004 stopped")    w.Done()  }()  w.Wait()  log.Println("All servers stopped. Exiting.")  os.Exit(0)}

overseer

package mainimport (  "fmt"  "net/http"  "time"  "github.com/jpillora/overseer")//see example.sh for the use-case// BuildID is compile-time variablevar BuildID = "0"//convert your 'main()' into a 'prog(state)'//'prog()' is run in a child processfunc prog(state overseer.State) {  fmt.Printf("app#%s (%s) listening.../n", BuildID, state.ID)  http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    duration, err := time.ParseDuration(r.FormValue("duration"))    if err != nil {      http.Error(w, err.Error(), 400)      return    }    time.Sleep(duration)    w.Write([]byte("Hello World"))    fmt.Fprintf(w, "app#%s (%s) says hello/n", BuildID, state.ID)  }))  http.Serve(state.Listener, nil)  fmt.Printf("app#%s (%s) exiting.../n", BuildID, state.ID)}//then create another 'main' which runs the upgrades//'main()' is run in the initial processfunc main() {  overseer.Run(overseer.Config{    Program: prog,    Addresses: []string{":5005", ":5006"},    //Fetcher: &fetcher.File{Path: "my_app_next"},    Debug:  false, //display log of overseer actions  })}

對比

對比示例的操作步驟

  • 分別構建上面的示例,并記錄pid
  • 調用API,在其未返回時,修改內容(Hello World -> Hello Harry),重新構建。查看舊API是否返回舊的內容
  • 調用新API,查看返回的內容是否是新的內容
  • 查看當前運行的pid,是否與之前一致

下面給一下操作命令

# 第一次構建項目go build grace.go# 運行項目,這時就可以做內容修改了./grace &# 請求項目,60s后返回curl "http://127.0.0.1:5001/sleep?duration=60s" &# 再次構建項目,這里是新內容go build grace.go# 重啟,2096為pidkill -USR2 2096# 新API請求curl "http://127.0.0.1:5001/sleep?duration=1s"# 第一次構建項目go build endless.go# 運行項目,這時就可以做內容修改了./endless &# 請求項目,60s后返回curl "http://127.0.0.1:5003/sleep?duration=60s" &# 再次構建項目,這里是新內容go build endless.go# 重啟,22072為pidkill -1 22072# 新API請求curl "http://127.0.0.1:5003/sleep?duration=1s"# 第一次構建項目go build -ldflags '-X main.BuildID=1' overseer.go# 運行項目,這時就可以做內容修改了./overseer &# 請求項目,60s后返回curl "http://127.0.0.1:5005/sleep?duration=60s" &# 再次構建項目,這里是新內容,注意版本號不同了go build -ldflags '-X main.BuildID=2' overseer.go# 重啟,28300為主進程pidkill -USR2 28300# 新API請求curl http://127.0.0.1:5005/sleep?duration=1s

對比結果

 

示例 舊API返回值 新API返回值 舊pid 新pid 結論
grace Hello world Hello Harry 2096 3100 舊API不會斷掉,會執行原來的邏輯,pid會變化
endless Hello world Hello Harry 22072 22365 舊API不會斷掉,會執行原來的邏輯,pid會變化
overseer Hello world Hello Harry 28300 28300 舊API不會斷掉,會執行原來的邏輯,主進程pid不會變化

 

原理分析

可以看出grace和endless是比較像的。

  • 監聽信號
  • 收到信號時fork子進程(使用相同的啟動命令),將服務監聽的socket文件描述符傳遞給子進程
  • 子進程監聽父進程的socket,這個時候父進程和子進程都可以接收請求
  • 子進程啟動成功之后,父進程停止接收新的連接,等待舊連接處理完成(或超時)
  • 父進程退出,升級完成

overseer是不同的,主要是overseer加了一個主進程管理平滑重啟,子進程處理鏈接,能夠保持主進程pid不變。

如下圖表示的很形象

Golang,平滑重啟

自己實現

我們下面來嘗試自己實現下第一種處理,代碼如下,代碼來自《熱重啟golang服務器》:

package mainimport (  "context"  "errors"  "flag"  "log"  "net"  "net/http"  "os"  "os/exec"  "os/signal"  "syscall"  "time")var (  server  *http.Server  listener net.Listener  graceful = flag.Bool("graceful", false, "listen on fd open 3 (internal use only)"))func sleep(w http.ResponseWriter, r *http.Request) {  duration, err := time.ParseDuration(r.FormValue("duration"))  if err != nil {    http.Error(w, err.Error(), 400)    return  }  time.Sleep(duration)  w.Write([]byte("Hello World"))}func main() {  flag.Parse()  http.HandleFunc("/sleep", sleep)  server = &http.Server{Addr: ":5007"}  var err error  if *graceful {    log.Print("main: Listening to existing file descriptor 3.")    // cmd.ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.    // when we put socket FD at the first entry, it will always be 3(0+3)    f := os.NewFile(3, "")    listener, err = net.FileListener(f)  } else {    log.Print("main: Listening on a new file descriptor.")    listener, err = net.Listen("tcp", server.Addr)  }  if err != nil {    log.Fatalf("listener error: %v", err)  }  go func() {    // server.Shutdown() stops Serve() immediately, thus server.Serve() should not be in main goroutine    err = server.Serve(listener)    log.Printf("server.Serve err: %v/n", err)  }()  signalHandler()  log.Printf("signal end")}func reload() error {  tl, ok := listener.(*net.TCPListener)  if !ok {    return errors.New("listener is not tcp listener")  }  f, err := tl.File()  if err != nil {    return err  }  args := []string{"-graceful"}  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 signalHandler() {  ch := make(chan os.Signal, 1)  signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)  for {    sig := <-ch    log.Printf("signal: %v", sig)    // timeout context for shutdown    ctx, _ := context.WithTimeout(context.Background(), 100*time.Second)    switch sig {    case syscall.SIGINT, syscall.SIGTERM:      // stop      log.Printf("stop")      signal.Stop(ch)      server.Shutdown(ctx)      log.Printf("graceful shutdown")      return    case syscall.SIGUSR2:      // reload      log.Printf("reload")      err := reload()      if err != nil {        log.Fatalf("graceful restart error: %v", err)      }      server.Shutdown(ctx)      log.Printf("graceful reload")      return    }  }}

代碼可參考:https://github.com/CraryPrimitiveMan/go-in-action/tree/master/ch4

關于這一部分,個人的理解也不是特別深入,如果又不正確的地方請大家指正。

參考文章

熱重啟golang服務器 

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VEVB武林網。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 日韩视频一区在线观看 | 免费黄色福利视频 | a久久| 国产精品视频免费 | 日韩欧美国产网站 | 久久精品久久久久电影 | 2018国产大陆天天弄 | 国产亚洲综合视频 | 嫩草影院网站入口 | 久久99久久精品 | 精品久久久久一区二区国产 | 日日草夜夜草 | 日韩日韩日韩日韩日韩日韩日韩 | 毛片毛片毛片毛片毛片毛片毛片毛片毛片毛片 | 成人网18免费网站 | 国产婷婷色综合av蜜臀av | 精品999| 国产精品成人一区二区三区夜夜夜 | 污视频网址在线观看 | 日本五月婷婷 | 亚洲一区二区三区日韩 | 国产精品久久久久久久久久久久久 | 久久久久久久久久久一区二区 | 国产欧美日韩综合精品一区二区 | 无套内谢孕妇毛片免费看红桃影视 | 色婷婷在线视频 | 欧美日韩在线看 | 国产精品久久 | 99re6在线| 久久精品国产一区二区电影 | 91精品国产91久久久久久最新 | 久久久国产精品视频 | 一级毛片观看 | 电影k8一区二区三区久久 | 国产一区二区三区在线免费观看 | 在线一区二区三区 | igao视频 | 夜夜春精品视频高清69式 | 按摩高潮japanesevideo | 久在线视频 | 欧美激情精品久久久久久 |