有了前面的 lua 热更新原理(一)做为铺垫,相信理解skynet热更新会容易点。html
可是有个问题是,skynet不能像前面讲的那样,从新require文件来达到热更的目的,为何?git
出于效率的考虑,云风大大修改了lua的官方实现,require文件时会缓存以前的代码,再次require该文件,检测到有缓存是不会加载新的文件的。参见:CodeCachegithub
不过那个也能够经过选项来控制,调用skynet.codecache.mode('OFF')就能够破除这个限制。除了经过这个开关,也能够调用skynet.codecache.clear(),这个在debug模式下有这个实现,能够参考skynet debug_console源码分析。缓存
但codecache有个不可忽视的问题,每次codecache后,无论代码有没有用到,skynet不会清理旧的内存。这会致使了屡次codecache后,skynet内存使用会愈来愈大服务器
这是为何?由于codecache后,只有新起的服务会用到新代码,旧的服务还引用着旧代码。而skynet没有作引用GC的复杂逻辑,在旧服务销毁时,没有清理用不到的旧代码。函数
这种方式能够用于简单的场景,例如在棋牌游戏中测试时每一局的发牌数据,就能够用这种方式来更新,而不用重启服务器。源码分析
若是要替换掉已运行lua文件的代码,则要用应用更高级的lua的更新机制,参见lua 热更新原理(二)测试
skynet中热更新具体是怎么操做的呢?ui
再介绍一个函数:lua
load (chunk [, chunkname [, mode [, env]]])
他的做用是加载chunk代码块,而且以env做为他的上值。咱们可让旧的值保存在env,而后更换新的chunk块,这样就能够达到热更的目的。若是env为nil,默认以全局环境_ENV做为其上值,这样chunk块代码共享全局变量。若是以元表{__index = _ENV}做为env,chunk模块仍然能够共享全局变量,可是加载后的全局环境却没有chunk块中的全局变量。
在skynet控制台中有个命令就是用来加载新的模块的:
function dbgcmd.RUN(source, filename) local inject = require "skynet.inject" local output = inject(skynet, source, filename , export.dispatch, skynet.register_protocol) collectgarbage "collect" skynet.ret(skynet.pack(table.concat(output, "\n"))) end
其中inject函数就是保存传入函数的上值:
local function getupvaluetable(u, func, unique) local i = 1 while true do local name, value = debug.getupvalue(func, i) if name == nil then return end local t = type(value) if t == "table" then u[name] = value elseif t == "function" then if not unique[value] then unique[value] = true getupvaluetable(u, value, unique) end end i=i+1 end end return function(skynet, source, filename , ...) if filename then filename = "@" .. filename else filename = "=(load)" end local output = {} local function print(...) local value = { ... } for k,v in ipairs(value) do value[k] = tostring(v) end table.insert(output, table.concat(value, "\t")) end local u = {} local unique = {} local funcs = { ... } for k, func in ipairs(funcs) do getupvaluetable(u, func, unique) end local p = {} local proto = u.proto if proto then for k,v in pairs(proto) do local name, dispatch = v.name, v.dispatch if name and dispatch and not p[name] then local pp = {} p[name] = pp getupvaluetable(pp, dispatch, unique) --1) end end end local env = setmetatable( { print = print , _U = u, _P = p}, { __index = _ENV }) local func, err = load(source, filename, "bt", env) if not func then return { err } end local ok, err = skynet.pcall(func) if not ok then table.insert(output, err) end return output end
在skynet.lua最后一段很不起眼的代码中:
local debug = require "skynet.debug" debug(skynet, { dispatch = skynet.dispatch_message, clear = clear_pool, suspend = suspend, })
结合上面的代码,咱们能够看到递归获取了skynet.dispatch_message,skynet.register_protocol函数的上值。正是在skynet.register_protocol函数中咱们定义了本身的回调函数。上面的1)中获取了回调函数的上值。在的回调函数中,咱们通常会如此写代码:
skynet.start(function() skynet.dispatch("lua", function (_, address, cmd, ...) local f = CMD[cmd] --2) if f then skynet.ret(skynet.pack(f(address, ...))) end end) end)
关键的执行代码就在2)处,而2)是回调函数的上值,因此咱们能够轻松的替换掉他,从而实现热更的目的。
其实以上的热更思路也只是替换了关键的函数,能不能作到不停机重启一个服务呢?这个看看云风大大的blog:
有机会再来剖析一下。
欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。
参看:
https://www.cnblogs.com/RainRill/p/8940673.html
https://blog.csdn.net/mycwq/article/details/53943890