欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang學習之平滑重啟

 更新時間:2018年08月16日 14:37:38   作者:瘋狂的原始人  
這篇文章主要介紹了Golang學習之平滑重啟,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

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

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

我們先來看下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的重啟。

使用方式

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

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

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

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

grace

package main

import (
  "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 main

import (
  "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 main

import (
  "fmt"
  "net/http"
  "time"

  "github.com/jpillora/overseer"
)

//see example.sh for the use-case

// BuildID is compile-time variable
var BuildID = "0"

//convert your 'main()' into a 'prog(state)'
//'prog()' is run in a child process
func 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 process
func 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
  • 調(diào)用API,在其未返回時,修改內(nèi)容(Hello World -> Hello Harry),重新構建。查看舊API是否返回舊的內(nèi)容
  • 調(diào)用新API,查看返回的內(nèi)容是否是新的內(nèi)容
  • 查看當前運行的pid,是否與之前一致

下面給一下操作命令

# 第一次構建項目
go build grace.go
# 運行項目,這時就可以做內(nèi)容修改了
./grace &
# 請求項目,60s后返回
curl "http://127.0.0.1:5001/sleep?duration=60s" &
# 再次構建項目,這里是新內(nèi)容
go build grace.go
# 重啟,2096為pid
kill -USR2 2096
# 新API請求
curl "http://127.0.0.1:5001/sleep?duration=1s"


# 第一次構建項目
go build endless.go
# 運行項目,這時就可以做內(nèi)容修改了
./endless &
# 請求項目,60s后返回
curl "http://127.0.0.1:5003/sleep?duration=60s" &
# 再次構建項目,這里是新內(nèi)容
go build endless.go
# 重啟,22072為pid
kill -1 22072
# 新API請求
curl "http://127.0.0.1:5003/sleep?duration=1s"


# 第一次構建項目
go build -ldflags '-X main.BuildID=1' overseer.go
# 運行項目,這時就可以做內(nèi)容修改了
./overseer &
# 請求項目,60s后返回
curl "http://127.0.0.1:5005/sleep?duration=60s" &
# 再次構建項目,這里是新內(nèi)容,注意版本號不同了
go build -ldflags '-X main.BuildID=2' overseer.go
# 重啟,28300為主進程pid
kill -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不會斷掉,會執(zhí)行原來的邏輯,pid會變化
endless Hello world Hello Harry 22072 22365 舊API不會斷掉,會執(zhí)行原來的邏輯,pid會變化
overseer Hello world Hello Harry 28300 28300 舊API不會斷掉,會執(zhí)行原來的邏輯,主進程pid不會變化

原理分析

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

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

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

如下圖表示的很形象

自己實現(xiàn)

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

package main
import (
  "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服務器

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • go?replay流量重放的實現(xiàn)

    go?replay流量重放的實現(xiàn)

    Goreplay?是用?Golang?寫的一個?HTTP?實時流量復制工具,支持流量放大縮小、限流、文件記錄及ES集成,本文主要介紹了go?replay流量重放的實現(xiàn),感興趣的可以了解一下
    2025-05-05
  • Go語言使用make進行內(nèi)存分配的代碼示例

    Go語言使用make進行內(nèi)存分配的代碼示例

    在Go語言里,內(nèi)存分配是程序開發(fā)中的關鍵環(huán)節(jié),make是Go語言內(nèi)置的一個重要函數(shù),專門用于為切片(slice)、映射(map)和通道(channel)這三種引用類型分配內(nèi)存并初始化,本文將深入剖析 make 函數(shù)的使用,結合代碼示例與實際項目場景,幫助開發(fā)者更好地掌握這一特性
    2025-06-06
  • 一文詳解如何使用Go語言生成二維碼

    一文詳解如何使用Go語言生成二維碼

    使用Go語言編程時,生成任意內(nèi)容的二維碼是非常方便的,下面這篇文章主要給大家介紹了關于如何使用Go語言生成二維碼的相關資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-01-01
  • go中string、int、float相互轉(zhuǎn)換的實現(xiàn)示例

    go中string、int、float相互轉(zhuǎn)換的實現(xiàn)示例

    本文主要介紹了go中string、int、float相互轉(zhuǎn)換的實現(xiàn)示例,文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • go語言方法集為類型添加方法示例解析

    go語言方法集為類型添加方法示例解析

    這篇文章主要為大家介紹了go語言方法集以及為類型添加方法示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2022-04-04
  • golang網(wǎng)絡數(shù)據(jù)包捕獲庫gopacket詳解

    golang網(wǎng)絡數(shù)據(jù)包捕獲庫gopacket詳解

    gopacket/pcap是Go語言網(wǎng)絡數(shù)據(jù)包捕獲庫,支持實時捕獲、過濾、解析pcap文件及接口統(tǒng)計,結合layers包可分析DNS流量,處理CNAME、多IP等復雜情況,需注意權限和性能優(yōu)化,適用于網(wǎng)絡監(jiān)控與安全工具開發(fā)
    2025-07-07
  • golang bad file descriptor問題的解決方法

    golang bad file descriptor問題的解決方法

    這篇文章主要給大家介紹了golang bad file descriptor問題的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-02-02
  • go讀取request.Body內(nèi)容踩坑實戰(zhàn)記錄

    go讀取request.Body內(nèi)容踩坑實戰(zhàn)記錄

    很多初學者在使用Go語言進行Web開發(fā)時,都會遇到讀取 request.Body內(nèi)容的問題,這篇文章主要給大家介紹了關于go讀取request.Body內(nèi)容踩坑實戰(zhàn)記錄的相關資料,需要的朋友可以參考下
    2023-11-11
  • golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • Golang?基礎面試題集錦

    Golang?基礎面試題集錦

    這篇文章主要為大家介紹了Golang?基礎面試題集錦,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09

最新評論