zoukankan      html  css  js  c++  java
  • skynet源码分析之sharedata共享数据

    在游戏开发中,需要用到大量且更新不频繁的配置数据,而把业务拆分到多个服务后,各个服务可能只用到其中的少部分数据,此时每个服务加载所有数据会浪费大量内存。sharedata模块就是为了解决这种需求设计的,其原理是:将共享lua数据存放到一个c结构里,所有服务都共享这个c结构的内存块,各个服务可以获取这个共享内存对象,然后就可以像读取普通lua表一样读取数据。

     1. 创建共享数据

    调用sharedata.new(name, value) api创建共享数据对象,主要2个参数:name,名字;v,可以是一张lua table,也可以是lua文本代码,也可以是lua文件,最终都会转化成一个table,然后调用c层接口(第15行)返回一个c结构共享内存,共享内存用引用计数管理其生命周期。

     1 -- lualib/skynet/sharedata.lua
     2 function sharedata.new(name, v, ...)
     3     skynet.call(service, "lua", "new", name, v, ...)
     4 end
     5 
     6 function CMD.new(name, t, ...)
     7     local dt = type(t)
     8     local value
     9     ...
    10     newobj(name, value)
    11 end
    12 
    13 local function newobj(name, tbl)
    14     assert(pool[name] == nil)
    15     local cobj = sharedata.host.new(tbl)
    16     sharedata.host.incref(cobj)
    17     local v = { value = tbl , obj = cobj, watch = {} }
    18     objmap[cobj] = v
    19     pool[name] = v
    20     pool_count[name] = { n = 0, threshold = 16 }
    21 end

    主要数据结构如下:struct table,是返回给lua层的c结构。

    // lualib-src/lua-sharedata.c
    struct table {  //单个共享对象结构,供lua层操作
        int sizearray; //一维数据长度
        int sizehash; //hash数据长度
        uint8_t *arraytype; //一维数据类型,integer、boolean、string or table
        union value * array; //一维数据值
        struct node * hash; //hash数据值,是一个数组,每个元素是一个hash数据信息
        lua_State * L; //lua虚拟栈,可获取共享的lua table数据
    };
    
    struct node { //table里一个key-value值对应的数据信息
        union value v;
        int key;        // integer key or index of string table
        int next;       // next slot index
        uint32_t keyhash;
        uint8_t keytype;        // key type must be integer or string
        uint8_t valuetype;      // value type can be number/string/boolean/table
        uint8_t nocolliding;    // 0 means colliding slot
    };

    共享数据创建完后,各个服务通过sharedata.query(name)查询共享对象,对象引用计数加一,使用者可以向读取lua表一样读取数据,但其实际上是一个userdata(c结构),所以在corelib里定义了userdata的元表,元表中包含__index、__len、__pairs等方法。比如,__index方法会调用到c层的接口获取指定key对应的value(第5行)。

    1 -- lualib/skynet/sharedata/corelib.lua
    2 local index = core.index
    3 function meta:__index(key)
    4     local obj = getcobj(self)
    5     local v = index(obj, key)
    6     ...
    7     return v
    8 end

    2. 更新共享对象

    当需要更新共享对象时,调用sharedata.update(name, v, ...) api,其原理是:创建一个新的共享对象(第17行),然后把旧对象标记为dirty(第20行)。

     1 -- lualib/skynet/sharedata.lua
     2 function sharedata.update(name, v, ...)
     3     skynet.call(service, "lua", "update", name, v, ...)
     4 end
     5 
     6 function CMD.update(name, t, ...)
     7     local v = pool[name]
     8     local watch, oldcobj
     9     if v then
    10         watch = v.watch
    11         oldcobj = v.obj
    12         objmap[oldcobj] = true
    13         sharedata.host.decref(oldcobj)
    14         pool[name] = nil
    15         pool_count[name] = nil
    16     end
    17     CMD.new(name, t, ...)
    18     local newobj = pool[name].obj
    19     if watch then
    20         sharedata.host.markdirty(oldcobj)
    21         for _,response in pairs(watch) do
    22             response(true, newobj)
    23         end
    24     end
    25     collect10sec()  -- collect in 10 sec
    26 end

    此时,已引用该对象的服务并不会马上更新,而是等到下一次使用该共享对象才会判断是否要刷新,即惰性更新。

    第4行,如果对象标记为dirty,说明需要更新,调用c库接口进行更新。

     1 -- lualib/skynet/sharedata/corelib.lua
     2 local function getcobj(self)
     3     local obj = self.__obj
     4     if isdirty(obj) then
     5         local newobj, newtbl = needupdate(self.__gcobj)
     6         if newobj then
     7             local newgcobj = newtbl.__gcobj
     8             local root = findroot(self)
     9             update(root, newobj, newgcobj)
    10             if obj == self.__obj then
    11                 error ("The key [" .. genkey(self) .. "] doesn't exist after update")
    12             end
    13             obj = self.__obj
    14         end
    15     end
    16     return obj
    17 end

    对于旧对象,如果没有服务引用(即引用计数为0时),在下一次collectobj时会删掉它并清理内存(第12行)。

     1 -- service/sharedatad.lua
     2 local function collectobj()
     3     while true do
     4          skynet.sleep(100)       -- sleep 1s
     5          if collect_tick <= 0 then
     6              collect_tick = 600      -- reset tick count to 600 sec
     7              collectgarbage()
     8              for obj, v in pairs(objmap) do
     9                  if v == true then
    10                      if sharedata.host.getref(obj) <= 0  then
    11                          objmap[obj] = nil
    12                          sharedata.host.delete(obj)
    13                      end
    14                  end
    15              end
    16         else
    17             collect_tick = collect_tick - 1
    18         end
    19     end
    20 end

    调用sharedata.delete(name) 将共享对象的引用计算减一。

  • 相关阅读:
    ActivityGroup简单介绍
    退役笔记一#MySQL = lambda sql : sql + &#39; Source Code 4 Explain Plan &#39;
    敏捷开发流程总结
    TI C66x DSP 系统events及其应用
    AssemblyInfo.cs文件的作用
    angularjs基本执行流程
    美丽的表格样式(使用CSS样式表控制表格样式)
    DOS命令大全--具体解释
    《海量数据库解决方式》读后感
    Linux内核设计基础(十)之内核开发与总结
  • 原文地址:https://www.cnblogs.com/RainRill/p/9019614.html
Copyright © 2011-2022 走看看