net/http 库启动服务后,平滑重启:
    需要做哪些事?

    • 服务启动:使用TCP启动连接、结合端口,启动服务
    • 拷贝进程:fork一个子进程,其中需要复制进程元数据,重造一个。
    • 上下文监听:获取系统信号、关闭。 ```java package main

    import ( “context” “encoding/json” “flag” “fmt” “net” “net/http” “os” “os/signal” “path/filepath” “syscall” “time” )

    type listener struct { Addr string json:"addr" FD int json:"fd" Filename string json:"filename" }

    func importListener(addr string) (net.Listener, error) { // 从系统中获取编码后的监听端口元数据 listenerEnv := os.Getenv(“LISTENER”) if listenerEnv == “” { return nil, fmt.Errorf(“unable to find LISTENER environment variable”) }

    1. // Unmarshal the listener metadata.
    2. // 解析元数据
    3. var l listener
    4. err := json.Unmarshal([]byte(listenerEnv), &l)
    5. if err != nil {
    6. return nil, err
    7. }
    8. if l.Addr != addr {
    9. return nil, fmt.Errorf("unable to find listener for %v", addr)
    10. }
    11. // 通过额外的元数据文件,重建端口监听
    12. listenerFile := os.NewFile(uintptr(l.FD), l.Filename)
    13. if listenerFile == nil {
    14. return nil, fmt.Errorf("unable to create listener file: %v", err)
    15. }
    16. defer listenerFile.Close()
    17. // Create a net.Listener from the *os.File.
    18. ln, err := net.FileListener(listenerFile)
    19. if err != nil {
    20. return nil, err
    21. }
    22. return ln, nil

    }

    func createListener(addr string) (net.Listener, error) { // 启动TCP监听服务连接 ln, err := net.Listen(“tcp”, addr) if err != nil { // 此处会返回监听失败信息 return nil, err }

    1. return ln, nil

    }

    func createOrImportListener(addr string) (net.Listener, error) { // Try and import a listener for addr. If it’s found, use it. ln, err := importListener(addr) if err == nil { fmt.Printf(“Imported listener file descriptor for %v.\n”, addr) return ln, nil }

    1. // 当端口启动失败,证明这个端口已经被占用
    2. ln, err = createListener(addr)
    3. if err != nil {
    4. return nil, err
    5. }
    6. fmt.Printf("Created listener file descriptor for %v.\n", addr)
    7. return ln, nil

    }

    func getListenerFile(ln net.Listener) (os.File, error) { switch t := ln.(type) { case net.TCPListener: return t.File() case *net.UnixListener: return t.File() } return nil, fmt.Errorf(“unsupported listener: %T”, ln) }

    func forkChild(addr string, ln net.Listener) (*os.Process, error) { // 获取端口和元数据放在子进程中。 lnFile, err := getListenerFile(ln) if err != nil { return nil, err } defer lnFile.Close() // 获取端口 l := listener{ Addr: addr, FD: 3, Filename: lnFile.Name(), } listenerEnv, err := json.Marshal(l) if err != nil { return nil, err }

    1. // Pass stdin, stdout, and stderr along with the listener to the child.
    2. // 获取系统输入输出流文件
    3. files := []*os.File{
    4. os.Stdin,
    5. os.Stdout,
    6. os.Stderr,
    7. lnFile,
    8. }
    9. // Get current environment and add in the listener to it.
    10. // 获取环境和添加端口
    11. environment := append(os.Environ(), "LISTENER="+string(listenerEnv))
    12. // 获取进程名称和文件
    13. execName, err := os.Executable()
    14. if err != nil {
    15. return nil, err
    16. }
    17. execDir := filepath.Dir(execName)
    18. // 开个子进程
    19. p, err := os.StartProcess(execName, []string{execName}, &os.ProcAttr{
    20. Dir: execDir,
    21. Env: environment,
    22. Files: files,
    23. Sys: &syscall.SysProcAttr{},
    24. })
    25. if err != nil {
    26. return nil, err
    27. }
    28. // 返回系统进程
    29. return p, nil

    }

    func waitForSignals(addr string, ln net.Listener, server *http.Server) error { // 到底用多少缓冲合适呢?需要根据自己的服务大小?我觉得不需要,于是用1 //signalCh := make(chan os.Signal, 1024) signalCh := make(chan os.Signal, 1) // 此处可以接收很多种信息,本例子主要是接收SIGHUP信号,从而fork一个进程 // SIGHUP终止收到该信号的进程,用于重启。 // SIGINT强制结束进程 // SIGQUIT结束进程和dump core signal.Notify(signalCh, syscall.SIGHUP, syscall.SIGUSR2, syscall.SIGINT, syscall.SIGQUIT) // 出于保护机制,选择for、select、case来进行 读取channel。因为case可以保护channel在panic情况下不报错 for { select { case s := <-signalCh: fmt.Printf(“%v 信号接收.\n”, s) switch s {

    1. case syscall.SIGHUP:
    2. // fork一个子分支进程,保障运行后,再去关闭服务。即使有服务进来,也不会受到影响,依然运行。
    3. p, err := forkChild(addr, ln)
    4. if err != nil {
    5. fmt.Printf("fork子分支失败: %v.\n", err)
    6. continue
    7. }
    8. fmt.Printf("Forked child子分支Pid: %v.\n", p.Pid)
    9. // 创建一个上下文,当关机时,超过5秒算是超时。
    10. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    11. defer cancel()
    12. // 返回关机时遇到的任何报错
    13. return server.Shutdown(ctx)
    14. case syscall.SIGUSR2:
    15. // fork一个子分支进程.
    16. p, err := forkChild(addr, ln)
    17. if err != nil {
    18. fmt.Printf("fork子分支失败: %v.\n", err)
    19. continue
    20. }
    21. // 打印这个PID,等待更多信号
    22. fmt.Printf("Forked child %v.\n", p.Pid)
    23. case syscall.SIGINT, syscall.SIGQUIT:
    24. // 创建一个上下文,当关机时,超过5秒算是超时。
    25. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    26. defer cancel()
    27. // 关闭期间报错,则返回错误
    28. return server.Shutdown(ctx)
    29. }
    30. }
    31. }

    }

    func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “Hello from %v!\n”, os.Getpid()) }

    func startServer(addr string, ln net.Listener) *http.Server { http.HandleFunc(“/hello”, handler)

    1. httpServer := &http.Server{
    2. Addr: addr,
    3. }
    4. go httpServer.Serve(ln)
    5. return httpServer

    }

    func main() { var addr string // 端口addr=8080 // 打印标杆在这里输出命令行。本机地址、端口号 flag.StringVar(&addr, “addr”, “:8080”, “Address to listen on.”) // 读者知道就好,不用扣这个细节 // 创建端口与服务. 启动一个goroutine 跑一个Http服务 ln是 listener端口 ln, err := createOrImportListener(addr) if err != nil { fmt.Printf(“Unable to create or import a listener: %v.\n”, err) // 系统退出 os.Exit(1) } // 启动服务 server := startServer(addr, ln)

    1. // 等待系统信号,fork一个进程或退出
    2. err = waitForSignals(addr, ln, server)
    3. if err != nil {
    4. fmt.Printf("Exiting: %v\n", err)
    5. return
    6. }
    7. fmt.Printf("Exiting.\n")

    }

    1. 如果是gin框架,又应该如何做呢?
    2. - 只需要把gin的路由带过来,其实也是共用net/http
    3. - 步骤和上面差不多,都是fork一个进程
    4. - 我们可以使用endless的库进行守护即可
    5. ```java
    6. package main
    7. import (
    8. "github.com/fvbock/endless"
    9. "github.com/gin-gonic/gin"
    10. "log"
    11. )
    12. func main() {
    13. gin.SetMode(gin.ReleaseMode)
    14. r := setRouter()
    15. if err := endless.ListenAndServe(":9090", r); err != nil {
    16. log.Fatalf("listen: %s\n", err)
    17. }
    18. }
    19. func setRouter() *gin.Engine {
    20. r := gin.Default()
    21. r.GET("/", func(context *gin.Context) {
    22. context.String(200, "hello world AAA!")
    23. })
    24. return r
    25. }

    具体想了解endless 做了哪些操作,可以去看一下endless.go