前言
本文论述如何应用endless+fsontify实现linux服务器上的热更新。原以为站点更新会像.net、java等那么不便,间接上传更新文件就会主动重启看到最新成果,但在golang中,须要咱们手动来实现。
惯例部署
步骤
go web在服务器上的部署步骤个别是打包成二进制文件部署在多台Linux服务器上,可搭配lvs、Nginx来做反向代理,实现负载平衡,保障站点的高可用。
问题
linux上二进制文件启动后,无奈间接替换,得有个手动重启的动作,这个对更新来说就十分麻烦了,而且程序员可能还不容许接触服务器,得通过运维同学来帮助,这显著不敌对。
现有解决方案
在网上浏览一番后,我总结了以下几种形式
- 主动编译 有一些框架提供了主动编译的性能,例如 gin、fresh、rizla。这些都须要go环境,原理是检测到code有变动,主动帮你做相似go bulid的事,这并不适宜服务器,服务器上不会去装go环境这么麻烦的货色,仅实用本地开发环境。
- 容器 应用docker等容器来部署
- endless 为了让服务不中断,优雅的进行重启,能够利用 endless 来替换规范库net/http的ListenAndServe
本文就论述第3种计划的实际。
实现
思路
站点的更新,首先要更新文件,接着要敞开原过程,重新启动。咱们就用程序来帮咱们实现这件事件,设定一个watch.conf的监控文件,利用fsontify来监听文件的变更,如果检测到变更,开始执行命令,受权新文件的权限:chmod 777 [binname],接着告诉以后过程:kill -1 [pid]。最初endless发挥作用,会把程序重新启动,并且应用的是原来的申请上下文。抽象的概括完之后,上面解说下要害局部。
watch.conf
在我的项目下新建test.watch.conf,内容如下
{ "HttpPort": "8080", "Ver": "1", "DelaySecond": 5, "BinName": "test"}
- HttpPort 是咱们站点的监听端口
- Ver 以后版本
- DelaySecond 提早多少秒启动,为了防止我的项目里有其余的资源没有上传胜利,设定一个缓冲的工夫,但个别就一个二进制文件要更新而已。
- BinName 你编译的二进制文件名,就是你go build后生成的。
fsontify监听watch.conf
watcher, err := fsnotify.NewWatcher() if err != nil { simpleLog(err) } defer watcher.Close() go func() { for { select { case event := <-watcher.Events: simpleLog("监听到事件event:", event, time.Now()) //if event.Op&fsnotify.Write == fsnotify.Write { // simpleLog("检测到文件批改") // restartChan <- true //} if event.Op&fsnotify.Remove == fsnotify.Remove { simpleLog("检测到文件被删除") restartChan <- true } case <-watcher.Errors: //fmt.Println("error:", err) } } }() err = watcher.Add(watchPath) if err != nil { simpleLog(err) log.Fatal(err) } <-make(chan bool)
监听到fsnotify.Write或者fsnotify.Remove,(看大家应用的上传文件形式,形式不同,事件可能不一样),而后给通道restarChan发送音讯,示意能够更新了。
执行命令
for { <-restartChan simpleLog("收到告诉 筹备 restartWeb") m := loadConf() if m == nil { simpleLog("conf 解析失败") } else { curVer = m.Ver binName := m.BinName //延时x秒后再执行重启,以防有的文件还没上传实现 time.Sleep(time.Duration(m.DelaySecond) * time.Second) curPid = strconv.Itoa(syscall.Getpid()) simpleLog("获取最新过程pid", curPid) //给文件受权 simpleLog("chmod 777", binName) exec.Command("chmod", "777", binName).Run() //告诉以后过程敞开 simpleLog("kill -1", curPid) exec.Command("kill", "-1", curPid).Run() simpleLog("更新实现", curPid, binName) } }
期待通道restarChan
,当收到音讯,示意能够开始执行命令了;loadConf()
读取.conf文件的构造,序列化到struct;给新的二进制文件受权exec.Command("chmod", "777", binName).Run()
;获取以后的过程PID,最初执行exec.Command("kill", "-1", curPid).Run()
告诉本人的过程,留神是-1,不是-9,有差异,-9就杀死过程,启动不起来的。
endless
g := gin.New() //输入pid和版本信息 g.GET("/hello", func(c *gin.Context) { pid := strconv.Itoa(syscall.Getpid()) simpleLog("/hello", pid) c.JSON(200, gin.H{"message": "Hello Gin new2!", "ver": curVer, "pid": pid}) }) m := loadConf() curVer = m.Ver s := endless.NewServer(":"+m.HttpPort, g) err := s.ListenAndServe() if err != nil { simpleLog("server err: %v", err) } simpleLog("Server on " + m.HttpPort + " stopped") os.Exit(0)
选用的web框架是gin,原先用的是iris,发现不行。当然不必web框架,用mux.NewRouter()
也能够的。
总结
linux上的站点目录如下,一个test go的二进制文件,一个.conf监控文件。
变更watch.conf后记录的日志
如果有任何不对的或者倡议,欢送指出,独特改良。
我的项目传到github上了,须要源码的同学戳这里:https://github.com/imleaf/go_...