前言
当go语言开发的server应用已经在运行时,如果更新了代码,直接编译并运行,那么不好意思,端口已经在使用中:
listen tcp :8000: bind: address already in use
看到这样的错误信息,我们通常都是一通下意识的操作:
lsof -i:8000
kill -9 …
这样做端口被占用的问题是解决了,go程序也成功更新了。但是这里面还隐藏着两个问题:
- kill程序时可能把正在处理的用户请求给中断了
- 从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)
}
原理
其使用非常简单,实现代码也很少,但是很强大,下面我们看看她的实现:
kill -1
endless的使用方法是先编译新程序,并执行"kill -1 旧进程id",我们看看旧程序接收到-1信号之后作了什么:
func (srv *endlessServer) handleSignals() {
...
for {
sig = <-srv.sigChan
srv.signalHooks(PRE_SIGNAL, sig