背景:

更新配置文件、更新server程序等,需要重启服务器,需要做到重启服务器时,服务器不停止运行,请求不丢失

原理:

热重启原理涉及到一些系统调用及父子进程之间文件句柄的传递等较多细节,处理过程大致如下:

  1. 监听信号
  2. 收到 kill -1 信号时, fork子进程(使用相同的启动命令),将文件描述符、环境参数等传递给子进程
  3. 这时父进程、子进程同时存在
  4. 子进程启动后,父进程停止执行服务,停止接受请求,父进程等到旧链接、业务处理完成(或超时)
  5. 父进程退出,重启完成

serve.go

package goo

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
)

type Serve struct {
	ShutDown   bool
	ChanSignal chan os.Signal
}

func NewServe() *Serve {
	c := make(chan os.Signal)
	signal.Notify(c)

	serve := Serve{
		ChanSignal: c,
	}

	serve.pid()

	go serve.run()

	return &serve
}

func (this *Serve) run() {
	for s := range this.ChanSignal {
		switch s {
		case syscall.SIGHUP:
			execSpec := &syscall.ProcAttr{
				Env:   os.Environ(),
				Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
			}
			if _, err := syscall.ForkExec(os.Args[0], os.Args, execSpec); err != nil {
				log.Fatalln(err.Error())
			}
			this.ShutDown = true
			signal.Stop(this.ChanSignal)

		case syscall.SIGKILL, syscall.SIGINT:
			this.ShutDown = true
			signal.Stop(this.ChanSignal)
		}
	}
}

func (this *Serve) pid() error {
	file, err := os.Create(".pid")
	if err != nil {
		return err
	}
	defer file.Close()
	file.WriteString(fmt.Sprintf("%d", os.Getpid()))
	return nil
}

func (this *Serve) Stop() {
	os.Exit(0)
}

 main.go

package main

import (
	"fmt"
	"time"

	"googo.co/goo"
)

var (
	serve *goo.Serve
)

func init() {
	serve = goo.NewServe()
}

func main() {
	num := 0
	c := make(chan string)

	for {
		num = num + 1

		go SyncArticle(c)

		for {
			msg := <-c

			if msg == "end" {
				if serve.ShutDown {
					serve.Stop()
				}
				time.Sleep(time.Duration(config.IDataApi.Time) * time.Minute)
				break
			}

			fmt.Println(time.Now().Format("2006-01-02 15:04:05"), num, msg)
		}
	}
}