可热更服务,热更脚本支持

前言

对于有状态的服务,利用新服务替换旧服务的方式,旧服务我们一直会维持到没有状态存在为止,当有简单逻辑bug的时候,旧服务没法立即更新,没法完美的解决问题,这时候可能就需要用到热更脚本来解决突发的一些简单逻辑bug。

限制

热更脚本对比滚服更新的最大区别就是:

  • 热更脚本
    是在旧的lua服务中完成

  • 滚服更新
    启动新lua服务,流量切入新的lua服务

使用滚服更新的好处是,我们不需要考虑热更后对于服务内部状态的影响,编码方式可以随意使用,闭包,函数式编程,随便用。
想安全可靠的使用热更脚本更新,我们必须要限制我们的编码行为,才能做到功能按照预期完成更新。

限制一 匿名函数生成

假设我们需要热更模块如下:

hello.lua
1
2
3
4
5
6
7
8
9
10
11
local M = {}

function M.create_count()
local a = 0
return function()
a = a + 1
return a
end
end

return M
1
2
3
4
5
6
7
local hello = require "hello"

function main()
local count = hello.create_count()
--hotfix
local count2 = hello.create_count()
end

如果我们想把a = a + 1改成 a = a + 2,那么main函数中的count是热更不到的,所以可热更脚本模块,不能使用返回闭包函数。

限制二 修改全局变量

改动全局变量本身就是非常危险的行为,需要禁止。

限制三 模块返回值必须是table,并且其他模块引用时都需要从表根部开始引用

假设如下例子

mode1.lua
1
2
3
4
5
6
7
local M = {}

function M.hello()
"hello skynet"
end

return M
main.lua
1
2
3
4
5
6
7
local mode1 = require "mode1"

local hello = mode1.hello()
function main()
hello()
mode1.hello()
end

热更后脚本

mode1.lua
1
2
3
4
5
6
7
local M = {}

function M.hello()
"hello skynet_fly"
end

return M

这样热更后,main中的hello是用的旧版本,而 mode1.hello是新版本。

限制四 模块 key 对应的value 类型不能变

突然改变类型,大概率是非常危险的行为。

实现方案

实现之前,有仔细的研究下云大的snax中实现的热更方案
云大的方案是通过注入的方式,刷更新脚本。热更流程会把热更到的旧版函数upvalue挂到新版函数upvalue,主要考虑的是引用之前的状态数据。
而我觉得不想被热更的状态数据完成可以挂到一个不热更的模块中,这样热更并不会重置它,所以我的做法是把M中的key value,替换到旧版的。不处理upvalue,再新增一个挂载状态数据的模块即可。

具体实现

云大的热更方式,需要写额外的热更脚本,并不是在原文件直接热更,但是实际项目中,我们并不想额外写一个热更脚本,就想在原文件上修改,然后一键热更。
实际项目中,大多时候我们只需要热更经常改动的业务逻辑代码,所以我的做法是区分可热更脚本不可热更脚本,可热更脚本用一个特殊的require加载,这样也能方便记录到哪些脚本文件是需要检测热更的,启动之后,就能把信息记录到文件中,方便利用shell脚本检测热更。

如何使用

hotfix_require 引入

热更的脚本,需要使用hotfix_require加载。

make/script/check_hotfix.sh make/script/hotfix.sh

make_server.sh 增加了这2个脚本的生成。

  • make/script/check_hotfix.sh 检测并执行热更
  • make/script/hotfix.sh 手动指定热更脚本

state_data.lua

用于挂载状态数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local state_data = require "skynet-fly.hotfix.state_data"

local state_map = state_data.alloc_table("state_map")

local M = {}

-------------------------------test1----------------------------
--[[
有些状态数据,我们并不想随着热更被重置
]]

function M.test1()
if not state_map.a then
state_map.a = 0
end
state_map.a = state_map.a + 1
return state_map.a
end

--[[
预期结果: 热更后数据不能被重置
]]
return M

需要记录状态数据的,在可热更脚本中这样写即可,热更后state_map还是之前的table

热更完成的回调

1
2
3
4
5
6
7
local M = {}

function M.hotfix()

end

return M

可热更脚本中可以实现hotfix函数,热更成功后调用。

使用限制

只有可热更服务中可以使用。

不要热更服务之间调用的CMD命令

假设有2个服务 A B

A服务
1
2
3
4
5
6
7
8
9
10
11
12
13
local CMD = {}

--热更前
function CMD.add(a,b)
return a + b
end

--热更后
function CMD.add(a,b,c)
return a + b + c
end

return CMD
B服务
1
2
3
4
5
6
7
8
9
10
11
12
local CMD = {}

--热更前
function CMD.do_add()
local count = call('A', 'add', 1, 2)
end
--热更后
function CMD.do_add()
local count = call('A', 'add', 1, 2, 3)
end

return CMD

由于AB是2个服务,热更过程并不是串行的,就可能出现

  • 旧B调用新A
    调用之后 A肯定要报错,c参数不存在。

  • 新B调用旧A
    结果不符合预期,c参数没加上。

尽量不要在模块中改写其他模块的内容

1
2
3
4
5
6
7
8
9
local cccc = require "cccc"

local M = {}

function cccc.xxxx()

end

return M

像这种,如果热更失败了,cccc.xxxx的函数也成新版了。


可热更服务,热更脚本支持
https://huahua132.github.io/2024/07/27/skynet_fly_word/word_3/O_hotfix/
作者
huahua132
发布于
2024年7月27日
许可协议