热更新

  1. 动态脚本更新 (php)

  2. 短链接无状态服务器热更 (http)

  3. 长连接有状态,数据跟逻辑分离

    • golang Plugin热更 (.so 动态库更新) —-逻辑热更
  4. 长连接有状态,在运维层面, 利用容器部署热更(docker, k8s)

    • 服务不接受新连接,处理完剩余连接的逻辑,主动关闭。由已经更新过的服务接收新连接处理逻辑。

golang插件更新

下面代码实现的需求:

  1. 不修改主进程代码,不重新编译,在运行过程中 (包括不修改plugin.so文件名)
  2. 修改插件代码,重新编译插件,给主进程发送指令,主进程重新加载插件。

plugin文件必须main包

plugin
package main

import (
	"fmt"
)

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

main文件

maing.go
package main

import (
	"fmt"
	"os"
	"os/signal"
	"plugin"
	"pro2d/src/components/logger"
	"syscall"
)

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

func main() {
	stopChan := make(chan os.Signal)
	signal.Notify(stopChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)

	userChan := make(chan os.Signal)
	signal.Notify(userChan, syscall.SIGUSR1, syscall.SIGUSR2)
	LoadPlugin()

	for {
		select {
		case <-stopChan:
			fmt.Println("stop...")
			return
		case <-userChan:
			fmt.Println("userChan recv...")
			LoadPlugin()
		}
	}
}

Makefile文件

Makefile
IMGTIME := $(shell date "+%G%m%d_%H%M%S")
pname = plugin-$(IMGTIME).so

all:plugin build
	./plumain

reall:plugin
	ps -ef|grep plumain |grep -v grep | awk '{print $$2}'  | xargs -I {} kill -USR1 {}

build:
	go build -o ./plumain plumain.go

plugin:
	go build --buildmode=plugin -o ./$(pname) ./*.go
	rm -rf plugin.so && ln -s $(pname) plugin.so

.PHONY: all build plugin

Makefile文件中使用shell变量需要$$,比如上面 awk '{print $$2}'

编译 & 运行

$ make 

Hello, I am PluginA!

修改插件函数PluginA内容

golang
package main

import (
	"fmt"
)

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

给进程发送USR1自定义指令, 让程序重新加载plugin.so

$ make reall
ps -ef|grep plumain |grep -v grep | awk '{print $$2}'  | xargs -I {} kill -USR1 {}

可以看到 进程输出

Hello, I am PluginB!

注意的点

  1. plugin插件必须在main包下

  2. golang文件会检测plugin.so文件是否已经加载。

    • 解决办法1: 可以使用不同的文件名
    • 解决办法2: 编译插件时可以指定编译参数 -ldflags -pluginpath="plugin/hot-1" (不推荐)
  3. 怎么做到不修改plugin.so文件名字。

    • 解决办法: 每次新插件的生成新的文件名。然后使用软连接连接到固定的 plugin.so 上。可以参考Makefile文件的make reall
$ rm -rf plugin.so && ln -s $(pname) plugin.so
  1. golang的plugin只支持linux & macosx系统。

--完--