golang实现热更新的常规方式

热更新

去年写了一个Agent的程序,用于收集生产服务器的一些数据,以及对应的一些自动化操作等, 写完之后经常要修修改改加一些新功能, 产线服务器数量就很多, 导致了每次更新都是个大动作,目前的做法是通过puppet管理,新版本就往puppet上丢,等他自动重启即可,由此联想到了老东家游戏服务的热加载,所以看了一下golang的热加载实现。

基本流程

第一种方式, 文件主体更新

  1. golang服务进程运行时监听USR2信号
  2. 进程收到USR2信号后, 下载新版本的客户端到本地
  3. fork子进程(启动新版本服务)
  4. 将上下文, 句柄等信息交到新的子进程
  5. 新进程开始监听socket请求
  6. 等待旧服务连接停止
ch := make(chan os.Signal, 10)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
for {
    sign := <-ch
    switch sign
    case syscall.SIGUSR2:
        if err := StartNewPro(); err != nil {
            ......
            break
        }
        execSpec := &syscall.ProcAttr{
            Env: os.Environ(),
            Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
        }
        fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)
        ......
        process, _ := os.FindProcess(os.Getppid())
        process.Signal(syscall.SIGHUB)
        ......
    }
  • 注意: syscall 在windows 下无法使用,所以最开始我用了个最简单,也很low的办法处理windows, 即在windows 的服务收到了SIGUSR2的信号后,直接os/exec 调用个reload.sh的脚本, 其实也可以实现类似效果,但socket不会进行复制。

当然自己造轮子也不错,当然自己实现可能会有一些BUG之类的,常规的HTTP服务可以直接用endless和grace。

    func main() {
        app := gin.New()// 项目中时候的是gin框架
        router.Route(app)
        var server *http.Server
        server = &http.Server{
            Addr:    ":8080",
            Handler: app,
        }
        gracehttp.Serve(server)
    }
    func main() {
        endPoint := fmt.Sprintf(":%d", setting.HTTPPort)
        server := endless.NewServer(endPoint, routers.InitRouter())
        server.BeforeBegin = func(add string) {
            log.Printf("Actual pid is %d", syscall.Getpid())
        }
    }

第二种方式, 只更新配置文件

这种比较简单,接受USR1的信号进行配置文件,然后关闭新链接,等待所有connect 都为空了,再重新加载,代码比较简单,就不赘述了。

第三种方式, 基于plugin的方式进行更新
写过C++或者做过运维的一般都了解,C的代码发布经常是只更新 .so 文件, 这里的.so即是Linux的动态链接库,其作用是节省程序主体的大小,且可以灵活更新,golang的plugin即是和其原理类似,主要程序主体逻辑不修改,只改动插件的代码,即发布可以做到非常灵活。

plugina.go:

package main

import (
    "fmt"
)

func IamPluginA() {
    fmt.Println("Hello, I am PluginA!")
}

编译成插件

go build --buildmode=plugin -o plugina.so plugina.go

代码主体

package main

import (
    "fmt"
    "os"
    "plugin"
)

func main() {
    p, err := plugin.Open("./plugina.so")
    if err != nil {
        fmt.Println("error open plugin: ", err)
        os.Exit(-1)
    }
    s, err := p.Lookup("IamPluginA")
    if err != nil {
        fmt.Println("error lookup IamPluginA: ", err)
        os.Exit(-1)
    }
    if x, ok := s.(func()); ok {
        x()
    }
}

代码代码也比较简单,打开文件对象,找到函数,然后对函数对象(这会还是个interface{})进行断言成函数,最后执行该函数。

个人公众号, 分享一些日常开发,运维工作中的日常以及一些学习感悟,欢迎大家互相学习,交流

在这里插入图片描述

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用[1]:根据题主的需求构思,实现贪吃蛇的逻辑可以分为以下几个步骤。首先,需要确定游戏元素,包括蛇、墙、食物、分数和基本的提示信息。然后,根据用户故事,实现蛇的移动和交互,包括蛇撞墙死亡、蛇吃食物增加分数和身体长度、通过键盘控制蛇的移动方向以及通过按下esc键退出游戏。最后,需要进行界面初始化和游戏的开始和更新控制。[1] 引用[2]:在具体的实现过程中,可以创建蛇和食物的结构体,并进行初始化。蛇的结构体可以包括长度、坐标和方向等属性。食物的结构体可以包括坐标属性。然后,可以进行界面的初始化,包括游戏界面的显示和食物的初始化。最后,通过控制程序的更新周期来实现游戏的开始和进行。[2] 引用[3]:在使用Golang实现贪吃蛇的过程中,可以借助C语言的控制台程序来实现一些特定的功能,比如移动控制台的光标、获取键盘输入和隐藏控制台光标等。可以创建一个clib包来引入C语言的控制台程序,并在Go中嵌入C语言的函数来调用这些功能。例如,可以使用C.gotoxy函数来设置控制台光标的位置,使用C.direct函数来获取键盘输入的字符,使用C.hideCursor函数来隐藏控制台光标等。[3] 综上所述,可以使用Golang实现贪吃蛇游戏,通过创建蛇和食物的结构体,并进行初始化,然后进行界面的初始化和游戏的开始和更新控制。同时,可以借助C语言的控制台程序来实现一些特定的功能。希望这些信息对你有帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值