从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 error
	files := []*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.Stdout
	cmd.Stderr = os.Stderr
	cmd.ExtraFiles = files
	cmd.Env = env

	err = 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 bool
	var netListen net.Listener
	var err error
	args := 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中传递的套接字

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Nginx是一个高性能的开源Web服务器,作为一个反向代理服务器,它可以接收客户端请求并将其转发给后端的服务器进行处理。而Golang是一种快速、高效且易于使用的编程语言,也被广泛用于开发网络应用。 使用NginxGolang进行反向代理可以带来许多好处。首先,Nginx作为反向代理服务器,具有负载均衡的能力,可以将客户端请求分发给多个后端服务器,从而提高系统的可靠性和可扩展性。同时,Nginx还能够缓存静态文件,减轻后端服务器的负担,提高系统的性能。 而Golang作为后端服务器的开发语言,具有并发执行的能力,可以处理大量的请求。它的编译速度非常快,且可以将应用程序编译成独立的二进制文件,无需依赖其他库。这意味着,我们可以方便地在多个服务器之间部署Golang应用程序,而不需要为每个服务器都安装Golang环境。 在实际使用,我们可以将Nginx作为反向代理服务器,通过配置Nginx的代理规则,将客户端的请求转发给后端的Golang服务器。同时,可以通过Nginx的负载均衡策略,将请求分发给多个Golang服务器,以提高系统的可用性和性能。 总结来说,使用NginxGolang进行反向代理可以实现负载均衡、高可用性和高性能的网络应用。Nginx作为反向代理服务器能够高效地处理客户端请求,并将其转发给后端的Golang服务器进行处理。而Golang作为后端服务器的开发语言,具有并发执行和快速编译的优势,适合用于处理大量的请求。通过结合使用NginxGolang,我们可以构建出稳定、可靠且高性能的网络应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值