Beego中的热重启是以一个单独的模块而存在的,你可以在其他项目单独使用,例如:
import(
"log"
"net/http"
"os"
"strconv"
"github.com/astaxie/beego/grace"
)
func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("WORLD!"))
w.Write([]byte("ospid:" + strconv.Itoa(os.Getpid())))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", handler)
err := grace.ListenAndServe("localhost:8080", mux)
if err != nil {
log.Println(err)
}
log.Println("Server on 8080 stopped")
os.Exit(0)
}
打开两个终端
一个终端输入:ps -ef|grep 应用名
一个终端输入请求:curl "http://127.0.0.1:8080/hello"
热升级
kill -HUP 进程 ID
打开一个终端输入请求:curl "http://127.0.0.1:8080/hello?sleep=0"
在使用beego的框架时,你可以在配置文件中加上如下配置项:
#Listen config(system) Graceful = true
则会在beego项目中配置了热启动,只需要 kill -HUP pid 这样的命令就可以热升级项目
具体 beego 中的 grace 的原理是什么呢?我们通过查看源码来看看:
首先我们看下 beego.Run()函数:
// Run beego application.
// 省略了不少代码,我们只关注 grace相关的 http
func (app *App) Run(mws ...MiddleWare) {
addr := BConfig.Listen.HTTPAddr
if BConfig.Listen.HTTPPort != 0 {
addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort)
}
var (
err error
l net.Listener
endRunning = make(chan bool, 1)
)
// run cgi server
if BConfig.Listen.EnableFcgi {
}
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
app.Server.ErrorLog = logs.GetLogger("HTTP")
// run graceful mode
//
if BConfig.Listen.Graceful { // 在配置文件中开启grace 热重启
httpsAddr := BConfig.Listen.HTTPSAddr
app.Server.Addr = httpsAddr
if BConfig.Listen.EnableHTTP {
go func() {
server := grace.NewServer(addr, app.Handlers) // 调 grace 模块里面的server函数,初始化一些成员变量
server.Server.ReadTimeout = app.Server.ReadTimeout // 设置连接的读超时时间
server.Server.WriteTimeout = app.Server.WriteTimeout // 设置连接的写超时时间
if BConfig.Listen.ListenTCP4 {
server.Network = "tcp4"
}
if err := server.ListenAndServe(); err != nil {
logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
time.Sleep(100 * time.Microsecond)
endRunning <- true
}
}()
}
<-endRunning
return
}
<-endRunning
}
我们看下 server.ListenAndServe() 函数:
// ListenAndServe listens on the TCP network address srv.Addr and then calls Serve
// to handle requests on incoming connections. If srv.Addr is blank, ":http" is
// used.
func (srv *Server) ListenAndServe() (err error) {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
go srv.handleSignals() // 启动信号监听器
l, err := srv.getListener(addr) // 设置 listen监听文件描述符
if err != nil {
log.Println(err)
return err
}
srv.GraceListener = newGraceListener(l, srv)
if srv.isChild { // 如果是使用 grace 热重启
process, err := os.FindProcess(os.Getppid()) //找到父进程id
if err != nil {
log.Println(err)
return err
}
err = process.Signal(syscall.SIGTERM) // 给父进程发送 SIGTERM信号
if err != nil {
return err
}
}
log.Println(os.Getpid(), srv.Addr)
return srv.Serve() // 启动 Server
}
下面继续看 srv.Server() 函数:
// Serve accepts incoming connections on the Listener l,
// creating a new service goroutine for each.
// The service goroutines read requests and then call srv.Handler to reply to them.
func (srv *Server) Serve() (err error) {
srv.state = StateRunning
err = srv.Server.Serve(srv.GraceListener)
log.Println(syscall.Getpid(), "Waiting for connections to finish...")
srv.wg.Wait() // 等待直到该进程的所有请求都断开,才退出,这个对应每个连接来了都会使用 srv.wg.Add(1), 然后在连接关闭的时候调用 srv.wg.Done()
srv.state = StateTerminate
return
}
再看下信号监听处理程序那:
// handleSignals listens for os Signals and calls any hooked in function that the
// user had registered with the signal.
func (srv *Server) handleSignals() {
var sig os.Signal
signal.Notify(
srv.sigChan,
hookableSignals...,
)
pid := syscall.Getpid() // 获取当前的进程id
for {
sig = <-srv.sigChan
srv.signalHooks(PreSignal, sig)
switch sig {
case syscall.SIGHUP: //这里就是 grace 热重启的子进程处理
log.Println(pid, "Received SIGHUP. forking.")
err := srv.fork()
if err != nil {
log.Println("Fork err:", err)
}
case syscall.SIGINT:
log.Println(pid, "Received SIGINT.")
srv.shutdown() //关闭进程
case syscall.SIGTERM:
log.Println(pid, "Received SIGTERM.")
srv.shutdown()
default:
log.Printf("Received %v: nothing i care about...\n", sig)
}
srv.signalHooks(PostSignal, sig)
}
}
svr.fork 是启动一个新的子进程:
func (srv *Server) fork() (err error) {
regLock.Lock()
defer regLock.Unlock()
if runningServersForked {
return
}
runningServersForked = true
var files = make([]*os.File, len(runningServers))
var orderArgs = make([]string, len(runningServers))
for _, srvPtr := range runningServers {
switch srvPtr.GraceListener.(type) {
case *graceListener:
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.GraceListener.(*graceListener).File() // 获取监听的文件描述符
default:
files[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.tlsInnerListener.File()
}
orderArgs[socketPtrOffsetMap[srvPtr.Server.Addr]] = srvPtr.Server.Addr
}
....
args = append(args, "-graceful") // 添加上 -graceful 参数,标识为是重新启动了新的子进程,为了设置 svr.isChild() 参数,用于处理一些逻辑
if len(runningServers) > 1 {
args = append(args, fmt.Sprintf(`-socketorder=%s`, strings.Join(orderArgs, ",")))
log.Println(args)
}
cmd := exec.Command(path, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = files // 继承父进程监听的文件描述符()
err = cmd.Start() // 启动一个新的子进程
if err != nil {
log.Fatalf("Restart: Failed to launch, error: %v", err)
}
return
}
总结:
(1)如何实现优雅关闭
grace 在每次创建一个连接的时候使用 wg.Add(1), 然后关闭连接的时候使用 wg.Done(), 使用 wg.Wait() 来保证处理完用户的请求之后再关闭。
(2)如何启动一个新的进程呢?
监听 HUP 信号,当接收到 HUP 信号的时候,重启启动一个新的子进程来处理请求(继承父进程的监听文件描述符)。
(3)何时关闭父进程
子进程启动的时候,获取父进程的id,并给父进程发送 TREM 信号,然后父进程设置 status 为关闭状态并关闭父进程监听的文件描述符,当父进程所有的连接都断开了,父进程就会退出程序。