应用程序优雅的退出和热升级

应用程序部署上线正式运行后,都会遇到程序升级维护的问题,一般情况下都直接kill掉运行中的应用程序,然后更新程序,启动程序。这样进行操作会遇到一个问题就是,在kill掉程序的时候,不能保证当前程序是没有进行业务处理的,如果存在请求,当前的业务会失败,如果是获取数据,不会产生太大的问题,如果是其它情况,有可能会产生错误数据或脏数据,如何优雅的解决程序升级但是又不影响正在运行的业务的运行(如何进行热升级)?

进行程序的热升级的必要条件:

  1. 应用程序的更新
  2. 正在运行的程序停止数据的接入(停止端口监听),处理完接入的请求后自动退出
  3. 启动更新后的应用程序,并进行数据接入。

如何监听程序的退出操作

程序的启动和退出,操作系统都会发出相对应的信号,程序可以监听对应的信号来进行处理。golang中,可以通过signal包中的Notify进行信号的监听。

package main

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

func main() {
	signals := make(chan os.Signal, 10)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)

	select {
	case sg := <-signals:
		fmt.Println(sg)
	}
}

需要注意的是,不同的操作系统对信号的处理方式是不一样的。golang的信号主要是对类unix的操作系统。对于window系统,control+c会触发os.Interrupt,对于信号的支持可以参考godoc。

如何统计判断当前正在进行的业务数

golang中的http server中有一个connstate的函数变量,可以用于监听请求的链接状态变化。

package main

import (
	"fmt"
	"net"
	"net/http"
)

func main() {
	http.HandleFunc("/hello", func(resp http.ResponseWriter, req *http.Request) {
		resp.Write([]byte("hello"))
	})
	s := http.Server{
		Addr: "0.0.0.0:9090",
		ConnState: func(c net.Conn, s http.ConnState) {
			fmt.Println(c.RemoteAddr(), s.String())
		},
	}
	s.ListenAndServe()
}

可以在ConnState中通过监听state的变化来计数接入数判断当前正在处理中的业务数。需要注意的是,http的复用,有可能多次请求,只有一个new的state

如何优雅的退出程序

  1. 接收操作系统发送的SIGINT信号,阻塞退出
  2. 监听请求链接数变化,链接数为0且收到sigint信号出发真正的退出信号
package main

import (
	"fmt"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	http.HandleFunc("/hello", func(resp http.ResponseWriter, req *http.Request) {
		time.Sleep(time.Second * 20)
		resp.Write([]byte("hello"))
	})

	exit := make(chan int, 1) //用于mark是否真的退出
	iscalllexit := false      //用于mark是否接收到exit signal
	count := 0                //用于mark 正在处理请求数
	s := http.Server{
		Addr: "0.0.0.0:9090",
		ConnState: func(c net.Conn, s http.ConnState) {
			fmt.Println(count, s)
			switch s { //需要注意的是并发的问题,只是用来演示原理
			case http.StateActive:
				count++
			case http.StateIdle:
				count--
			}
			if count == 0 && iscalllexit { //链接数为0且受到exit信号
				exit <- 1
			}
		},
	}

	ln, err := net.Listen("tcp", s.Addr)
	if err != nil {
		fmt.Println(err)
	}

	go s.Serve(ln)

	signals := make(chan os.Signal, 10)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)

	select {
	case <-signals:
		ln.Close()
		iscalllexit = true
		fmt.Println("exit")
		if i := <-exit; i != 0 {
			return
		}
	}
	fmt.Println("exit success")
}

如何进行热升级

使用os.StartProcess启动新的应用程序(由于原来的应用程序的端口已经释放,新的程序可以重新进行监听)

针对热更新,facebook有完整的实现。https://github.com/facebookarchive/httpdown(优雅的处理完后退出)
https://github.com/facebookarchive/grace(优雅的程序升级)

posted @ 2019-10-22 18:39  zhao379028604  阅读(462)  评论(0编辑  收藏  举报