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) 將共享對象的引用計算減一。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM