golang 熱重啟


熱重啟的意義

  • 可以讓用戶神不知鬼不覺的,更新后端的項目
  • 測試的時候,也不用停止項目然后再開啟項目,降低項目開啟時間

golang熱重啟的主要步驟

  • 將編譯好的項目覆蓋進行

golang熱重啟的思想

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

源碼

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"
	"flag"
)
var (
   listener net.Listener
   err error
   server http.Server
   graceful =  flag.Bool("g", false, "listen on fd open 3 (internal use only)")
)

func init(){
	log.SetFlags(log.Ldate | log.Lshortfile)
}
// MyHandler 控制台
type MyHandler struct {

}

func (*MyHandler)ServeHTTP(w http.ResponseWriter, r *http.Request){
   	log.Println("request start at ", time.Now(),  r.URL.Path+"?"+r.URL.RawQuery,  "request done at ", time.Now(), "  pid:", os.Getpid())
   	time.Sleep(10 * time.Second)
	fmt.Fprintln(w,"cout")
   	log.Println("request done at ", time.Now(), "  pid:", os.Getpid() )
   	log.Println(r.RemoteAddr)
}

func main() {
	flag.Parse()
   	fmt.Println("start-up at " , time.Now(), *graceful)
	if *graceful {
		// 這暗藏殺機,先復制fd到新的fd, 然后設置子進程exec時自動關閉父進程的fd,即“F_DUPFD_CLOEXEC”
		// 要是接收到信號后,不執行這里,就會發生端口被占用的錯誤
		f := os.NewFile(3, "")
		listener, err = net.FileListener(f)
		fmt.Printf( "graceful-reborn  %v %v  %#v \n", f.Fd(), f.Name(), listener)
	}else{
		listener, err = net.Listen("tcp", ":8080")
	}
   	server := http.Server{
    	Handler: &MyHandler{},
    	ReadTimeout: 6 * time.Second,
   	}
   	log.Printf("Actual pid is %d\n", syscall.Getpid())
   	if err != nil {
      	log.Println(err)
      	return
   	}
	log.Printf(" listener: %v\n",   listener)

   	go func(){//不要阻塞主進程
      	err := server.Serve(listener)
      	if err != nil {
         	log.Println(err)
      	}
   	}()

   	//signals
   	func(){
      	ch := make(chan os.Signal, 1)
      	signal.Notify(ch, syscall.SIGINT)
      	for{
			//阻塞主進程, 不停的監聽系統信號
         	sig := <- ch
        	log.Printf("signal: %v", sig)
         	ctx, _ := context.WithTimeout(context.Background(), 20*time.Second)  // 這里就是上下文
        	switch sig {
			// 我用終止信號,做例子,大家可以使用別的信號
        	case syscall.SIGINT:
            	println("signal cause reloading")
            	signal.Stop(ch)
            	{
					// fork子進程
               		tl, ok := listener.(*net.TCPListener)
               		if !ok {
                  		log.Println("listener is not tcp listener")
                  		return
					}
					// 接收父進程的socket文件描述符
               		currentFD, err := tl.File()
               		if err != nil {
                  		log.Println("acquiring listener file failed")
                  		return
					}
					//  好坑啊,這里,“-g"是必須的,因為再次運行之后,g=true
			   		cmd := exec.Command(os.Args[0],"-g")
			   		log.Println(cmd.Args)
					// currentFD 這里是子進程了,fork的時候,將socket文件描述傳給子進程
					// 父進程將socket文件描述符傳遞給子進程可以通過命令行
					// f := os.NewFile(3, "")  []*os.File{currentFD} 想到對應
               		cmd.ExtraFiles, cmd.Stdout,cmd.Stderr = []*os.File{currentFD} ,os.Stdout, os.Stderr
					err = cmd.Start()
					// 重啟運行   
               		if err != nil {
                  		log.Println("cmd.Start fail: ", err)
                  		return
               		}
               		log.Println("forked new pid : ",cmd.Process.Pid)
				}
				// 阻塞到父進程將所有的請求處理完畢
            	server.Shutdown(ctx) // 優雅的等待關閉
				log.Println("graceful shutdown at ", time.Now())
         	}
		}
   	}()
}
  • flag.Bool好坑啊!你設置默認值為false,但是你寫命令的時候,-g它的值就會變為true


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM