Golang: 使用 greuse 不中断更新服务

Published: 2021-07-22

Tags: Golang

本文总阅读量

Web 服务,直接重启会导致重启时间段的请求丢失,一个比较简便的优化方式是使用:greuse

地址:https://github.com/gogf/greuse

这个包工作的原理基于 SO_REUSEADDR and SO_REUSEPORT

可以同时启动多个程序来监听相同端口,当不同来路的请求到达后,内核会把请求“均衡负载”到多个实例上。

不中断的更新服务的流程:两个运行中的实例,先停止一个,替换程序包,启动新的实例,再重启旧的实例,完成更新。

关于 Socket 端口复用的介绍文章很多,摘录一些知识点:

From: 🔗socket 端口复用 SO_REUSEPORT 与 SO_REUSEADDR

  • 内核层面实现负载均衡。
  • 负载均衡算法:使用(remote_ip, remote_port, local_ip, local_port)来进行哈希,因此可以保证同一个client的包可以路由到同一个进程。
  • 有新连接建立时,内核只会唤醒一个进程来accept,并且保证唤醒的均衡性。
  • 安全性:第一个进程必须 enable 了这个选项之后,后续的进程才可以通过 enable 这个选项将socket绑定到同一个端口上。
  • 安全性:绑定到同一个端口的进程的 Effective user id 必须一致。

基础示例

package main

import (
    "fmt"
    "net/http"
)

func main() {

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello World!\n")
    })

    http.ListenAndServe(":8080", nil)
}

使用 Greuse 改造

package main

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

    "github.com/gogf/greuse"
)

func main() {
    listener, err := greuse.Listen("tcp", ":8881")
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    server := &http.Server{}
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "gid: %d, pid: %d\n", os.Getgid(), os.Getpid())
    })

    panic(server.Serve(listener))
}

Iris 框架基础示例

package main

import "github.com/kataras/iris/v12"

func main() {
    app := iris.New()

    root := app.Party("/")
    {
        root.Use(iris.Compression)
        root.Get("/", func(ctx iris.Context) {
          ctx.Writef("Hello World!\n")
        })
    }

    app.Listen(":8080")
}

Iris 框架使用 Greuse 改造

package main

import (
  "fmt"
  "github.com/gogf/greuse"
  "github.com/kataras/iris/v12"
)

func main() {
  app := iris.New()

  root := app.Party("/")
  {
    root.Use(iris.Compression)
    root.Get("/", func(ctx iris.Context) {
      fmt.Fprintf(ctx, "gid: %d, pid: %d\n", os.Getgid(), os.Getpid())
    })
  }

  listener, err := greuse.Listen("tcp", ":8080")
  if err != nil {
    panic(err)
  }

  if err := app.Run(iris.Listener(listener)); err != nil {
    fmt.Println("Failed to start web server")
  }
}

参考: