前言

当GO语言开发的server应用正在运行时, 如果更新了代码,直接编译并且运行,不好意思,端口已经在使用中:

listen tcp :8000: bind: address already in use

看到这样的错误信息,我们通常都是一通下意识的操作:

lsof -i:8000
kill -9 …

这样做端口被占用的问题是解决了,go程序也成功更新了。但是这里面还隐藏着两个问题:

  1. kill程序时可能把正在处理的用户请求给中断了
  2. 从kill到重新运行程序这段时间里没有应用在处理用户请求

关于如何解决这两个问题,网上有多种解决方案,今天我们谈谈endless的解决方案。

endless
endless的github地址为:https://github.com/fvbock/endless
她的解决方案是fork一个进程运行新编译的应用,该子进程接收从父进程传来的相关文件描述符,直接复用socket,同时父进程关闭socket。父进程留在后台处理未处理完的用户请求,这样一来问题1解决了。且复用soket也直接解决了问题2,实现0切换时间差。复用socket可以说是endless方案的核心。

使用
endless可以很方便的接入已经写好的程序,对于原生api,直接替换ListenAndServe为endless的方法,如下。并在编译完新的程序后,执行kill -1 旧进程id,旧进程便会fork一个进程运行新编译的程序。注:此处需要保证新编译的程序的路径和程序名和旧程序的一致。

func handler(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("WORLD!"))
}

func main() {
	mux1 := mux.NewRouter()
	mux1.HandleFunc("/hello", handler).
		Methods("GET")

	err := endless.ListenAndServe("localhost:4242", mux1)
	if err != nil {
		log.Println(err)
	}
	log.Println("Server on 4242 stopped")

	os.Exit(0)
}

对于使用gin框架的程序,可以以下面的方式接入:

r := gin.New()
r.GET("/", func(c *gin.Context) {
	c.String(200, config.Config.Server.AppId)
})
s := endless.NewServer(":8080", r)
err := s.ListenAndServe()
if err != nil {
	log.Printf("server err: %v", err)
	}

参考了这篇文章

作者讲解了原理,感兴趣可以去阅读原文。