zoukankan      html  css  js  c++  java
  • skynet:热更新 lua 代码

    skynet有两种方法热更新lua代码,clearcache和inject,文章分别对这两种方法做说明。

    clearcache热更新

    讲这个前,先说明下skynet代码加载的事情。因为skynet的每个服务都是一个独立的lua虚拟机,对于同一份lua代码,N个服务就要加载lua文件N次,所以,skynet做了优化,代码文件只需要加载一次到内存,其他服务复制这份内存就可以了,省了读取lua文件和解析lua语法的过程。
     
    clearcache 使用很简单,启动skynet,连接到其控制台
    # nc 127.0.0.1 8000
    Welcome to skynet console
    clearcache
    OK
    

     

    但clearcache有个不可忽视的问题,每次clearcache后,不管代码有没有用到,skynet不会清理旧的内存。这会导致了多次clearcache后,skynet内存使用会越来越大
    这是为什么?因为clearcache后,只有新起的服务会用到新代码,旧的服务还引用着旧代码。而skynet没有做引用GC的复杂逻辑,在旧服务销毁时,没有清理用不到的旧代码。
     
    或许你会很好奇,clearcache 没清的内存到底是啥?
    这要从skynet代码共享说起,skynet加载lua代码时,对于一个代码文件使用了一个新的vm加载,然后以文件名作为key将代码索引到全局的vm中。这样,当有服务需要代码了,就从全局vm找到代码,复制一份到服务。而clearcache,就是删除这个全局的vm,然后再重建一个。这么做的好处是,执行clearcache后,不影响已有服务的运行。问题是,全局vm删了,这个vm索引的所有代码没有清理,这样,那些加载代码用的vm没做清理。

    inject热更新

    inject命令相当于注入代码到服务中,原理就是让指定服务执行某个代码文件,通过修改模块及其函数的upvalue,完成对lua模块代码或变量的替换。这个命令我在前面的文章[1]有详细介绍。
     
    inject用法很简单,启动skynet,连接到其控制台:
    # nc 127.0.0.1 8000
    Welcome to skynet console
    list
    :00000004       snlua cmaster
    :00000005       snlua cslave
    :00000007       snlua datacenterd
    :00000008       snlua service_mgr
    :0000000a       snlua protoloader
    :0000000b       snlua console
    :0000000c       snlua debug_console 8000
    :0000000d       snlua simpledb
    OK
    inject :0000000d example/inject_simpledb.lua
    inject命令的难点是,这个要注入的lua代码该怎么写。
    下面直接改写skynet自带的example做说明:
    # cat examples/simpledb.lua
    local skynet = require "skynet"
    require "skynet.manager" 
    local db = {}
    local command = {}
    
    -- 增加了这里
    local function test(msg)
            print(msg)
    end
    -- 增加了这里
    function command.do_test(msg)
            test(msg)
    end
    
    skynet.start(function()
            skynet.dispatch("lua", function(session, address, cmd, ...)
                    local f = command[string.upper(cmd)]
                    if f then
                            skynet.ret(skynet.pack(f(...)))
                    else
                            error(string.format("Unknown command %s", tostring(cmd)))
                    end
            end)
            -- 增加了这里
            skynet.fork(function()
                    while true do
                            skynet.sleep(100)
                            command.do_test("itest!")
                    end
            end)
            skynet.register "SIMPLEDB"
    end)
    假设以上的 command.do_test 就是我们要热更改掉的函数。那用于inject的lua代码如下:
    # cat inject_test.lua
    if not _P then
            print("hotfix fail, no _P define")
            return
    end
    
    print("hotfix begin")
    
    -- 用于获取函数变量
    local function get_up(f)
            local u = {}
            if not f then
                    return u
            end
            local i = 1
            while true do
                    local name, value = debug.getupvalue(f, i)
                    if name == nil then
                            return u
                    end
                    u[name] = value
                    i = i + 1
            end
            return u
    end
    
    -- 获取原来的函数地址,及函数变量
    local command = _P.lua.command
    local upvs = get_up(command.do_test)
    local test = upvs.test
    
    command.do_test = function(msg)
        test('New ' .. msg)
    end
    
    print("hotfix end")

    启动控制台,执行inject后,就会看到类似下面的skynet的日志:

    # ./skynet examples/config
    [:00000001] LAUNCH logger 
    [:00000002] LAUNCH snlua bootstrap
    [:00000003] LAUNCH snlua launcher
    [:00000004] LAUNCH snlua cmaster
    [:00000005] LAUNCH snlua cslave
    [:00000006] LAUNCH harbor 1 16777221
    [:00000007] LAUNCH snlua datacenterd
    [:00000008] LAUNCH snlua service_mgr
    [:00000009] LAUNCH snlua main
    [:0000000a] LAUNCH snlua protoloader
    [:0000000b] LAUNCH snlua console
    [:0000000c] LAUNCH snlua debug_console 8000
    [:0000000d] LAUNCH snlua simpledb
    [:0000000e] LAUNCH snlua watchdog
    [:0000000f] LAUNCH snlua gate
    [:0000000f] Listen on 0.0.0.0:8888
    Watchdog listen on      8888
    [:00000009] KILL self
    [:00000002] KILL self
    itest!
    itest!
    itest!
    New itest!
    New itest!

    最后语

    通过前面的分析,我们知道了,clearcache和inject两种方法都可以热更代码。clearcache比较简单,但这种方法对于已有的服务是没有效果的,只有在新的服务才生效。而inject可以热更已有的服务,但不管是inject脚本的编写,还是inject命令的执行,都相对比较繁琐。所以要根据实际的需求,选择适合的方法热更lua代码。
     
     
    参考:
    [1] skynet 控制台管理使用技巧 没有开花的树
  • 相关阅读:
    pidgin的未认证解决办法
    题解【洛谷P1074】[NOIP2009]靶形数独
    题解【洛谷P1315】[NOIP2011]观光公交
    题解【BZOJ4145】「AMPPZ2014」The Prices
    题解【洛谷P4588】[TJOI2018]数学计算
    题解【洛谷P3884】[JLOI2009]二叉树问题
    题解【SP8002】HORRIBLE
    树链剖分学习笔记
    题解【洛谷P1807】最长路_NOI导刊2010提高(07)
    题解【洛谷P1995】口袋的天空
  • 原文地址:https://www.cnblogs.com/losophy/p/9204036.html
Copyright © 2011-2022 走看看