服务优雅重启 facebook/grace 简介

2023-05-24 19:47:14 浏览数 (3)

什么是服务优雅退出?

服务优雅退出是指在服务关闭时,让服务有足够的时间来处理完已接收的请求,避免任何数据的丢失。在服务退出时,需要先停止接收新的请求,等待所有已经接收的请求处理完毕,然后再关闭服务。这样做可以确保服务在关闭时不会影响服务的稳定性和数据的完整性。服务优雅退出通常是在编写服务时需要考虑的一个重要问题。

一个服务退出的示例

代码语言:txt复制
func main() {
    // 用于协程间通信,由信号监控协程,发消息给服务关闭协程
	stopC := make(chan struct{})
    // 监听服务退出信号
	go watchSignal(stopC)
    // 执行关闭服务前的操作,或手动关闭服务
	go shutdownServer(stopC)
    // 启动服务
    s.Serve()
    log.Info("服务已退出...")
}

// watchSignal 监听信号
func watchSignal(stop chan<- struct{}) {
	defer func() {
		if e := recover(); e != nil {
			errMsg := fmt.Sprintf("##catchSignal panic#main# error:%s", e)
			log.Fatal(errMsg)
		}
	}()
	// 保存PID
	if pid := syscall.Getpid(); pid != 1 {
		ioutil.WriteFile("server.pid", []byte(strconv.Itoa(pid)), 0777)
		defer os.Remove("server.pid")
	}
	// 创建 os.Signal 类型变量,用于接收系统信号
	signC := make(chan os.Signal, 1)
	// 使用 signal.Notify 函数将 os.Signal 变量和系统信号绑定
	signal.Notify(signC, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	for {
		// 监听信号
		s := <-signC
		log.Info("收到信号 -- "   s.String())
		switch s {
		case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
            // 释放资源
			releaseResource()
            // 开始关闭服务
			close(stop)
		default:
		}
	}
}

// 执行关闭服务前的操作,或手动关闭服务
func shutdownServer(stop <-chan struct{}) {
	<-stop
	log.Info("服务正在关闭...")
	// 执行服务退出逻辑
}

什么是服务优雅重启?

代码语言:txt复制
服务优雅重启,除了要优化退出旧服务之外,还需要考虑旧进程的请求处理,新请求的平滑切换。

一般重启,是先关闭旧服务,再启用新服务。因为要先关闭旧服务,再启动新服务,那么在就服务已关闭而新服务未重启完成的期间,请求是不可达的。这样的重启方式明显不够优雅。

优雅重启,是重启过程中,服务能够平滑地关闭所有连接,完成未处理完的请求,并重新启动服务。这样可以避免服务重启过程中可能出现的数据丢失或请求失败等问题,保证服务的高可用性和稳定性。

服务优雅重启通常需要先向服务发送一个信号,使其进入优雅关闭状态,等待所有连接关闭后再进行重启。在服务重启期间,系统会将新的请求转发至其他运行正常的服务实例,保证服务的持续可用性。

那么要如何做到优化重启呢?可以基于Facebook开发的Grace实现。

Grace 是什么?

Grace是Facebook开源的一个用于实现优雅重启的工具,它可以在不中断服务的情况下重新加载代码和配置。

Grace采用了双进程架构,即在子进程中启动新实例,将请求从父进程转发至子进程处理,等请求处理完成后再优雅地关闭父进程,这样就可以保证在重启过程中不会有请求被丢失或中断。

Grace 使用示例

代码语言:txt复制
package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"time"

	"github.com/facebookgo/grace/gracehttp"
)

var (
	address0 = flag.String("a0", ":48567", "Zero address to bind to.")
	address1 = flag.String("a1", ":48568", "First address to bind to.")
	address2 = flag.String("a2", ":48569", "Second address to bind to.")
	now      = time.Now()
)

func main() {
	flag.Parse()
	gracehttp.Serve(
		&http.Server{Addr: *address0, Handler: newHandler("Zero  ")},
		&http.Server{Addr: *address1, Handler: newHandler("First ")},
		&http.Server{Addr: *address2, Handler: newHandler("Second")},
	)
}

func newHandler(name string) http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/sleep/", func(w http.ResponseWriter, r *http.Request) {
		duration, err := time.ParseDuration(r.FormValue("duration"))
		if err != nil {
			http.Error(w, err.Error(), 400)
			return
		}
		time.Sleep(duration)
		fmt.Fprintf(w, "%s started at %s slept for %d nanoseconds from pid %d.n",
			name, now, duration.Nanoseconds(), os.Getpid())
	})
	return mux
}

0 人点赞