在上一篇博客介紹TOML配置的時(shí)候,講到了通過(guò)信號(hào)通知重載配置。我們?cè)谶@一篇中介紹下如何的平滑重啟server。
與重載配置相同的是我們也需要通過(guò)信號(hào)來(lái)通知server重啟,但關(guān)鍵在于平滑重啟,如果只是簡(jiǎn)單的重啟,只需要kill掉,然后再拉起即可。平滑重啟意味著server升級(jí)的時(shí)候可以不用停止業(yè)務(wù)。
我們先來(lái)看下Github上有沒(méi)有相應(yīng)的庫(kù)解決這個(gè)問(wèn)題,然后找到了如下三個(gè)庫(kù):
我們分別來(lái)學(xué)習(xí)一下,下面只講解http server的重啟。
使用方式
我們來(lái)分別使用這三個(gè)庫(kù)來(lái)做平滑重啟的事情,之后來(lái)對(duì)比其優(yōu)缺點(diǎn)。
這三個(gè)庫(kù)的官方都給了相應(yīng)的例子,例子如下:
但三個(gè)庫(kù)官方的例子不太一致,我們來(lái)統(tǒng)一一下:
我們參考官方的例子分別來(lái)寫(xiě)下用來(lái)對(duì)比的例子:
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 }) }
對(duì)比
對(duì)比示例的操作步驟
下面給一下操作命令
# 第一次構(gòu)建項(xiàng)目 go build grace.go # 運(yùn)行項(xiàng)目,這時(shí)就可以做內(nèi)容修改了 ./grace # 請(qǐng)求項(xiàng)目,60s后返回 curl "http://127.0.0.1:5001/sleep?duration=60s" # 再次構(gòu)建項(xiàng)目,這里是新內(nèi)容 go build grace.go # 重啟,2096為pid kill -USR2 2096 # 新API請(qǐng)求 curl "http://127.0.0.1:5001/sleep?duration=1s" # 第一次構(gòu)建項(xiàng)目 go build endless.go # 運(yùn)行項(xiàng)目,這時(shí)就可以做內(nèi)容修改了 ./endless # 請(qǐng)求項(xiàng)目,60s后返回 curl "http://127.0.0.1:5003/sleep?duration=60s" # 再次構(gòu)建項(xiàng)目,這里是新內(nèi)容 go build endless.go # 重啟,22072為pid kill -1 22072 # 新API請(qǐng)求 curl "http://127.0.0.1:5003/sleep?duration=1s" # 第一次構(gòu)建項(xiàng)目 go build -ldflags '-X main.BuildID=1' overseer.go # 運(yùn)行項(xiàng)目,這時(shí)就可以做內(nèi)容修改了 ./overseer # 請(qǐng)求項(xiàng)目,60s后返回 curl "http://127.0.0.1:5005/sleep?duration=60s" # 再次構(gòu)建項(xiàng)目,這里是新內(nèi)容,注意版本號(hào)不同了 go build -ldflags '-X main.BuildID=2' overseer.go # 重啟,28300為主進(jìn)程pid kill -USR2 28300 # 新API請(qǐng)求 curl http://127.0.0.1:5005/sleep?duration=1s
對(duì)比結(jié)果
示例 | 舊API返回值 | 新API返回值 | 舊pid | 新pid | 結(jié)論 |
---|---|---|---|---|---|
grace | Hello world | Hello Harry | 2096 | 3100 | 舊API不會(huì)斷掉,會(huì)執(zhí)行原來(lái)的邏輯,pid會(huì)變化 |
endless | Hello world | Hello Harry | 22072 | 22365 | 舊API不會(huì)斷掉,會(huì)執(zhí)行原來(lái)的邏輯,pid會(huì)變化 |
overseer | Hello world | Hello Harry | 28300 | 28300 | 舊API不會(huì)斷掉,會(huì)執(zhí)行原來(lái)的邏輯,主進(jìn)程pid不會(huì)變化 |
原理分析
可以看出grace和endless是比較像的。
overseer是不同的,主要是overseer加了一個(gè)主進(jìn)程管理平滑重啟,子進(jìn)程處理鏈接,能夠保持主進(jìn)程pid不變。
如下圖表示的很形象
自己實(shí)現(xiàn)
我們下面來(lái)嘗試自己實(shí)現(xiàn)下第一種處理,代碼如下,代碼來(lái)自《熱重啟golang服務(wù)器》:
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
關(guān)于這一部分,個(gè)人的理解也不是特別深入,如果又不正確的地方請(qǐng)大家指正。
參考文章
熱重啟golang服務(wù)器
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
標(biāo)簽:吐魯番 德宏 常州 貴州 曲靖 東營(yíng) 保定 許昌
巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Golang學(xué)習(xí)之平滑重啟》,本文關(guān)鍵詞 Golang,學(xué),習(xí)之,平滑,重啟,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。