背景:

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

原理:

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

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 {
			switch msg := <-c; msg {
			case "end":
				if serve.ShutDown {
					serve.Stop()
				}
				time.Sleep(time.Duration(config.IDataApi.Time) * time.Minute)
				break

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