热重启方式
- 使用负载均衡等技术分批次灰度机器
- 使用fork系统调用来实现平滑重启
go的平滑重启实现
具体原理就是子进程与父进程共享文件描述符,当开启子进程时文件描述符引用计数增加,父进程结束了引用计数减一
父进程调用close()并不会引发TCP连接断开从而使得子进程依旧可以处理来自客户端的请求
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"}
//这里利用exec启动一个新的进程
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
}
}
}
➜ go curl http://localhost:9999/hello
hello world233333!!!!#
2021/03/23 16:00:39 main: Listening on a new file descriptor.
2021/03/23 16:01:16 signal: user defined signal 2
2021/03/23 16:01:16 reload
2021/03/23 16:01:16 server.Serve err: http: Server closed
2021/03/23 16:01:16 main: Listening to existing file descriptor 3.
2021/03/23 16:01:17 graceful reload
2021/03/23 16:01:17 signal end
➜ go 2021/03/23 16:02:30 signal: user defined signal 2
2021/03/23 16:02:30 reload
2021/03/23 16:02:30 server.Serve err: http: Server closed
2021/03/23 16:02:30 main: Listening to existing file descriptor 3.
2021/03/23 16:02:43 graceful reload
2021/03/23 16:02:43 signal end
root 31631 1 0 16:01 pts/0 00:00:00 ./reboot -graceful
root 31901 31631 0 16:02 pts/0 00:00:00 ./reboot -graceful