skynet源码分析(十八)热更新操作

  Lua 脚本的热更新是快速开发和不停服修复 bug 的重要功能,基本上每个基于 Lua 的框架都会提供。但是因为 skynet 开启了众多的 LuaState 的特殊性,所以它的脚本热更新会跟别的框架有很大区别。本文会尝试讲解 skynet 中热更新的操作。

clearcache

  通过 debug 后台的 clearcache 指令可以清空进程的 CodeCache 缓存。更新本地文件,并且清空 CodeCache 的操作有时会被用来作为 skynet 热更新的方法。但是由于 CodeCache 清空时并不会修改旧的引用,所以旧的服务并不能用到更新,只有新启动的服务可以用到新的内容。
  因为这样的特性,导致了这种方法使用范围比较窄。如果服务本身就是不断创建和销毁的,那就可以直接用,旧的服务会按照以前的逻辑执行完毕,新的服务会直接用新的逻辑。比如像一些副本之类的功能可能就会这样设计,每个副本都对应了一个为它提供支持的服务,副本开始时创建服务,结束时销毁服务。
  还有一类服务也可以用这种方法热更新,就是无状态的服务。虽然旧的服务没办法被更新,但是因为是无状态服务,所以可以直接启动等量的新服务,然后让别的服务都把消息发送到新服务,旧的服务销毁即可。但是 skynet 并不提供相关的支持,要自己实现这类功能。

sharetable

  sharetable 是 skynet 提供的一种可以在不同 LuaState 中共享只读数据的操作,一般在项目中用于共享策划配置表数据。
  sharetable 中的数据可以进行热更新,针对同一份文件再次执行 loadfile 即可重新加载该文件,然后要在所有需要更新数据的服务里执行 sharetable.update 实现数据更新。

inject

  inject 是 skynet 提供的可以用于热更新的后台指令,它的特点比较鲜明,用起来比较麻烦,但是理论上可以覆盖所有情况。
  inject 会把该服务的 skynet.dispatch_message 和 skynet.register_protocol 这两个函数所有的 upvalue 作为 env 传给通过 load 加载的注入脚本,_U 是这两个函数的 upvalue,_P 是按协议类型分类的分发函数的 upvalue,想要替换任何函数或者是 upvalue 都需要自己在脚本中处理。
  这里给出一个尽量简单的例子,用来修改 example 里的 simpledb 服务,将 GET 改为每获取一次为原 value 后面追加一个 “A” 字母。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
local function getupvalues(func, target)
    if not func then return end
    local i = 1
    while true do
        local name, value = debug.getupvalue(func, i)
        if name == target then
            return value
        end
        i = i + 1
    end
end

local command = _P.lua.command
local db = getupvalues(command.GET, "db")

command.GET = function (key)
    db[key] = db[key] .. "A"
    return db[key]
end

print "inject success!!"

  首先使用任意 telnet 程序连上 skynet 的 debug 后台,这里要注意一下如果在启动 debug_console 时不指定监听地址的话,debug_console 默认监听的地址是 127.0.0.1,这样的话不能从远端连接该端口。
  输入 list,让 skynet 列出当前进程所有服务的地址。然后使用 inject 命令,指定目标服务的地址和要注入的脚本地址即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ nc 127.0.0.1 8000
Welcome to skynet console
list
:01000004       snlua cmaster
:01000005       snlua cslave
:01000007       snlua datacenterd
:01000008       snlua service_mgr
:0100000a       snlua protoloader
:0100000b       snlua console
:0100000c       snlua debug_console 8000
:0100000d       snlua simpledb
:0100000e       snlua watchdog
:0100000f       snlua gate
<CMD OK>
inject :0100000d examples/injectsimpledb.lua
inject success!!
<CMD OK>

  成功注入以后,启动客户端,向 simpledb 发送 GET 命令。可以看到成功返回,并且每次返回的值都在原来的值后面追加了一个 “A” 字母。

1
2
3
4
5
6
7
8
hello
result  worldA
hello
result  worldAA
hello
result  worldAAA
hello
result  worldAAAA
Licensed under CC BY-NC-SA 4.0
Built with Hugo
主题 StackJimmy 设计