乐趣区

关于golang:适用线上服务器的go-web自动重启的更新方案

前言

本文论述如何应用 endless+fsontify 实现 linux 服务器上的热更新。原以为站点更新会像.net、java 等那么不便,间接上传更新文件就会主动重启看到最新成果,但在 golang 中,须要咱们手动来实现。

惯例部署

步骤

go web 在服务器上的部署步骤个别是打包成二进制文件部署在多台 Linux 服务器上,可搭配 lvs、Nginx 来做反向代理,实现负载平衡,保障站点的高可用。

问题

linux 上二进制文件启动后,无奈间接替换,得有个手动重启的动作,这个对更新来说就十分麻烦了,而且程序员可能还不容许接触服务器,得通过运维同学来帮助,这显著不敌对。

现有解决方案

在网上浏览一番后,我总结了以下几种形式

  1. 主动编译 有一些框架提供了主动编译的性能,例如 gin、fresh、rizla。这些都须要 go 环境,原理是检测到 code 有变动,主动帮你做相似 go bulid 的事,这并不适宜服务器,服务器上不会去装 go 环境这么麻烦的货色,仅实用本地开发环境。
  2. 容器 应用 docker 等容器来部署
  3. 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_…

退出移动版