基于skyent的热更思考
- skynet-inject热更原理
- 关键源码分析
- 热更方式
- 拓扑图
- 注意事项
skynet-inject热更原理
inject是一个用于动态加载 Lua 代码文件并执行其中定义的函数的功能。可以在运行时动态加载 Lua 代码文件,然后调用其中定义的函数,通过修改模块及其函数的upvalue,实现代码的动态注入和执行。热更实现的大概思路:通过向指定服务注入cmd实现热更
参考wiki https://blog.codingnow.com/2016/11/lua_update.html
关键源码分析
-- debug_console.lualocal function adjust_address(address)local prefix = address:sub(1,1)if prefix == '.' thenreturn assert(skynet.localname(address), "Not a valid name")elseif prefix ~= ':' thenaddress = assert(tonumber("0x" .. address), "Need an address") | (skynet.harbor(skynet.self()) << 24)endreturn address
endfunction COMMAND.inject(address, filename, ...)address = adjust_address(address)local f = io.open(filename, "rb")if not f thenreturn "Can't open " .. filenameendlocal source = f:read "*a"f:close()local ok, output = skynet.call(address, "debug", "RUN", source, filename, ...)if ok == false thenerror(output)endreturn output
end
adjust_address: 用于调整传入的地址参数,对传入的地址参数进行转换,以保证将热更脚本注入到正确的服务地址
io.open(filename, “rb”): 以二进制只读的方式读取文件内容
source: 存放读取到的热更脚本内容
skynet.call(address, “debug”, “RUN”, …): 通过skynet的debug消息类型,将读取的热更脚本内容注入到目标服务地址(debug.RUN内部实现)
-- skynet.debug.luafunction dbgcmd.RUN(source, filename, ...)local inject = require "skynet.inject"local args = table.pack(...)local ok, output = inject(skynet, source, filename, args, export.dispatch, skynet.register_protocol)collectgarbage "collect"skynet.ret(skynet.pack(ok, table.concat(output, "\n")))
end
-- skynet.inject.luareturn function(skynet, source, filename, args, ...)if filename thenfilename = "@" .. filenameelsefilename = "=(load)"endlocal output = {}local function print(...)local value = { ... }for k,v in ipairs(value) dovalue[k] = tostring(v)endtable.insert(output, table.concat(value, "\t"))endlocal u = {}local unique = {}local funcs = { ... }for k, func in ipairs(funcs) dogetupvaluetable(u, func, unique)endlocal p = {}local proto = u.protoif proto thenfor k,v in pairs(proto) dolocal name, dispatch = v.name, v.dispatchif name and dispatch and not p[name] thenlocal pp = {}p[name] = ppgetupvaluetable(pp, dispatch, unique)endendendlocal env = setmetatable( { print = print , _U = u, _P = p}, { __index = _ENV })local func, err = load(source, filename, "bt", env)if not func thenreturn false, { err }endlocal ok, err = skynet.pcall(func, table.unpack(args, 1, args.n))if not ok thentable.insert(output, err)return false, outputendreturn true, output
end
这两段核心代码实现了一个动态加载和执行 Lua 代码的功能,通过构建环境表、加载代码并执行,最终返回执行结果。这个函数是实现动态注入功能的关键部分,能够实现在 Skynet 框架中动态加载并执行 Lua代码,完成注入热更脚本
热更方式
- 可以直接在debug控制台通过调用inject将指定代码注入到指定地址实现,但对于同类型的服务需要手动依次执行,效率较低
- 可通过实现sh脚本获取到指定服务地址(例如hotfix服),然后在框架中的hotfix服内实现对框架类型服务的热更lua脚本
- 实现sh脚本,用于启动debug控制台,执行热更逻辑
- 添加hotcfg,用于配置需要热更的服务与要执行lua脚本路径,并定义lua脚本
- 根据框架架构(单点 or 集群),在hotfix服务中实现获取指定服务组的addr
- 通过配置的hotcfg实现将lua热更脚本依次注入到目标服务中
拓扑图
方案1:
方案2:
注意事项
- 要注意新代码与原有代码的兼容性,避免因为接口变更或依赖关系导致运行时错误
- 要注意避免对全局环境造成非预期的影响,尽量将修改限制在局部代码范围内
- 需要加入足够的异常处理机制,以在热更新过程中出现异常情况时能够及时捕获并处理,以保证系统的稳定性
- 在进行热更新前最好制定好回滚策略,以便在更新失败时能够及时恢复到原有的稳定状态
- 尽量避免热更新过程对系统性能造成过大影响,可以在适当的时机进行优化,减少更新对系统性能的影响
- 在进行热更新时建议增加详细的日志记录和监控机制,以便随时监测更新过程中的各种信息并进行分析