詳解如何熱重啟golang服務器
更新時間:2018年08月16日 14:13:20 作者:black_ox
這篇文章主要介紹了詳解如何熱重啟golang服務器,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
服務端代碼經(jīng)常需要升級,對于線上系統(tǒng)的升級常用的做法是,通過前端的負載均衡(如nginx)來保證升級時至少有一個服務可用,依次(灰度)升級。
而另一種更方便的方法是在應用上做熱重啟,直接升級應用而不停服務。
原理
熱重啟的原理非常簡單,但是涉及到一些系統(tǒng)調(diào)用以及父子進程之間文件句柄的傳遞等等細節(jié)比較多。
處理過程分為以下幾個步驟:
- 監(jiān)聽信號(USR2)
- 收到信號時fork子進程(使用相同的啟動命令),將服務監(jiān)聽的socket文件描述符傳遞給子進程
- 子進程監(jiān)聽父進程的socket,這個時候父進程和子進程都可以接收請求
- 子進程啟動成功之后,父進程停止接收新的連接,等待舊連接處理完成(或超時)
- 父進程退出,升級完成
細節(jié)
- 父進程將socket文件描述符傳遞給子進程可以通過命令行,或者環(huán)境變量等
- 子進程啟動時使用和父進程一樣的命令行,對于golang來說用更新的可執(zhí)行程序覆蓋舊程序
- server.Shutdown()優(yōu)雅關閉方法是go1.8的新特性
- server.Serve(l)方法在Shutdown時立即返回,Shutdown方法則阻塞至context完成,所以Shutdown的方法要寫在主goroutine中
代碼
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 handler(w http.ResponseWriter, r *http.Request) { time.Sleep(20 * time.Second) w.Write([]byte("hello world233333!!!!")) } func main() { flag.Parse() http.HandleFunc("/hello", handler) server = &http.Server{Addr: ":9999"} 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(), 20*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 } } }
references
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
文字解說Golang Goroutine和線程的區(qū)別
goroutine 是 Go語言中的輕量級線程實現(xiàn),由 Go 運行時(runtime)管理,使用每一個 go 關鍵字將會額外開啟一個新的協(xié)程 goroutine,今天通過本文給大家介紹下Golang Goroutine和線程的區(qū)別,感興趣的朋友一起看看吧2022-03-03Go實現(xiàn)整合Logrus實現(xiàn)日志打印
這篇文章主要介紹了Go實現(xiàn)整合Logrus實現(xiàn)日志打印,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07詳解Go中如何進行進行內(nèi)存優(yōu)化和垃圾收集器管理
這篇文章主要為大家詳細介紹了Go中如何進行進行內(nèi)存優(yōu)化和垃圾收集器管理,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解下2023-11-11