重启更新Golang服务,不中断请求中的连接,请求会继续响应,实现服务平滑处理。

Github开源稳定解决方案

  • facebookgo/grace - Package grace provides a library that makes it easy to build socket based servers that can be gracefully terminated & restarted (that is, without dropping any connections).
  • fvbock/endless - Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3+)
  • jpillora/overseer - overseer is a package for creating monitorable, gracefully restarting, self-upgrading binaries in Go (golang).

gracedemo示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Command gracedemo implements a demo server showing how to gracefully
// terminate an HTTP server using grace.
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
}

重启更新示例操作流程如下:

  1. 构建示例并启动服务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 构建示例
GOOS=linux go build -o gracedemo gracedemo/demo.go

# 启动服务,启动后进程id: 3832021
./gracedemo &
[1] 3832021

# 访问服务
curl 'http://localhost:48567/sleep/?duration=1s'
Zero   started at 2023-07-13 11:11:36.732597048 +0800 CST m=+0.000495072 slept for 1000000000 nanoseconds from pid 3832021.

curl 'http://localhost:48567/sleep/?duration=20s'
Zero   started at 2023-07-13 11:11:36.732597048 +0800 CST m=+0.000495072 slept for 20000000000 nanoseconds from pid 3832021.

#  kill -USR2 3832021
[root@10-8-36-181 ~]#  curl 'http://localhost:48567/sleep/?duration=0s'
Zero   ------- started at 2023-07-13 11:14:31.781938014 +0800 CST m=+0.000556973 slept for 0 nanoseconds from pid 3832267.
  1. 平滑更新示例服务

修改示例响应结果代码如下:

1
2
3
4
5
6
7
8
fmt.Fprintf(
                        w,
                        "%s ------- started at %s slept for %d nanoseconds from pid %d.\n",
                        name,
                        now,
                        duration.Nanoseconds(),
                        os.Getpid(),
                )

重新构建并替换二进制文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 发起一个请求,并等待响应
curl 'http://localhost:48567/sleep/?duration=20s'

# 在请求响应前更新gracedemo服务
kill -USR2 3832021

# 发起一个新请求,响应结果显示已经更新
curl 'http://localhost:48567/sleep/?duration=0s'
Zero   ------- started at 2023-07-13 11:14:31.781938014 +0800 CST m=+0.000556973 slept for 0 nanoseconds from pid 3832267.

# 更新前未响应的请求,当sleep处理结束后,响应结果为旧的内容
curl 'http://localhost:48567/sleep/?duration=20s'
Zero   started at 2023-07-13 11:11:36.732597048 +0800 CST m=+0.000495072 slept for 20000000000 nanoseconds from pid 3832021.

参考