从nginx热更新聊一聊Golang中的热更新(下)

编程入门 行业动态 更新时间:2024-10-28 02:32:33

从<a href=https://www.elefans.com/category/jswz/34/1771256.html style=nginx热更新聊一聊Golang中的热更新(下)"/>

从nginx热更新聊一聊Golang中的热更新(下)

从nginx热更新聊一聊Golang中的热更新(下)

静态语言在服务器编程时都会遇到这样的问题:如何保证已有的连接服务不中断同时又升级版本?
在上一篇介绍热升级的时候时候,讲到了通过信号通知nginx进行热升级。我们在这一篇中介绍下平滑重启go http server。

目录结构

热更新

热更新目标:

  • 1、正在处理中的连接/服务/请求不能立即中断,需要继续提供服务
  • 2、socket对用户来说要保持可用,可以接受新的请求

直接沿用上篇的思路,热更新(单进程)流程,其基本流程如下:

  • 1、用新的bin文件去替换老的bin文件
  • 2、发送信号告知server进程(通常是USR2信号),进行平滑升级
  • 3、server进程收到信号后,通过调用 fork/exec 启动新的版本的进程
  • 4、子进程调用接口获取从父进程继承的 socket 文件描述符重新监听 socket
  • 5、老的进程不再接受请求,待正在处理中的请求处理完后,进程自动退出
  • 6、子进程托管给init进程

我们可以按照这个思路完成一个简单的可以热更新的http server

简易的http server

首先,我们需要一个最简单的http server

func main() {fmt.Println("Hello World!")var err error// 注册http请求的处理方法http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello world!"))})// 在8086端口启动http服务,其内部有一个循环accept 8086端口// 每当新的HTTP请求过来则开一个协程处理err = http.ListenAndServe("localhost:8086", nil)if err != nil {log.Println(err)}}

fork一个新的进程

在go语言里面可以有很多种方法fork一个新的进程,但是在这里我更倾向于推荐exec.Command接口来启动一个新的进程。因为Cmd struct中有一个ExtraFiles变量,子进程可以通过它直接继承文件描述符fd。

func forkProcess() error {var err errorfiles := []*os.File{gListen.File()} //demo only one //.File()path := "/Users/yousa/work/src/graceful-restart-demo/graceful-restart-demo"args := []string{"-graceful",}env := append(os.Environ(),"ENDLESS_CONTINUE=1",)env = append(env, fmt.Sprintf(`ENDLESS_SOCKET_ORDER=%s`, "0,127.0.0.1"))cmd := exec.Command(path, args...)//cmd := exec.Command(path, "-graceful", "true")cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrcmd.ExtraFiles = filescmd.Env = enverr = cmd.Start()if err != nil {log.Fatalf("Restart: Failed to launch, error: %v", err)return err}return nil
}

代码浅析:

在上面的files是存储父进程的文件描述符,path的内容是新的要替换的可执行文件的路径。

重要的一点是,.File()返回一个dup(2)的文件描述符。这个重复的文件描述符不会设置FD_CLOEXEC 标志,这个文件描述符操作容易出错,容易被在子进程中被错误关闭。

在其他语言(或者go里面)里面你可能通过使用命令行将文件描述符传递给子进程,在这里比较推荐使用ExtraFile传递fd。不过ExtraFiles在windows中不支持。

args中传递的-graceful参数是告诉子进程这是优雅热升级的一部分,这样子进程可以通过它知道,自己需要重用套接字而不是重新打开一个新的套接字

子进程初始化

func main() {fmt.Println("Hello World!")...var gracefulChild boolvar netListen net.Listenervar err errorargs := os.Args...if len(args) > 1 && args[1] == "-graceful" {gracefulChild = true} else {gracefulChild = false}fmt.Println("gracefulChild:", gracefulChild)if gracefulChild {//重用套接字log.Print("main: Listening to existing file descriptor 3.")f := os.NewFile(3, "")netListen, err = net.FileListener(f)} else {log.Print("main: Listening on a new file descriptor.")netListen, err = net.Listen("tcp", gServer.Addr)}if err != nil {log.Fatal(err)return}...
}

args用于解析入参,gracefulChild表示进程自己是否是子进程(对应到fork中的-graceful)(这里更推荐flag.BoolVar,但是写demo的时候使用起来有些问题,故临时使用args)

net.FileListener重用套接字,ExtraFiles中传递的套接字,

更多推荐

从nginx热更新聊一聊Golang中的热更新(下)

本文发布于:2024-02-17 07:16:07,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1693161.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:nginx   聊一聊   Golang

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!