skynet 共享数据原理

共享数据

目录

  1. 概述
  2. [sharedata 完整流程](#sharedata 完整流程)
    • [创建 sharedatad 服务](#创建 sharedatad 服务)
    • [sharedata.new() 流程](#sharedata.new() 流程)
    • [sharedata.query() 流程](#sharedata.query() 流程)
    • [sharedata.update() 流程](#sharedata.update() 流程)
  3. [sharedata 核心实现](#sharedata 核心实现)
    • [Lua 层实现](#Lua 层实现)
    • [C 层实现](#C 层实现)
    • 数据结构
  4. sharetable
  5. datacenter
  6. 对比与选择
  7. 最佳实践

概述

skynet 提供三种共享数据机制,用于在不同服务之间共享数据:

核心设计目标

  • 内存共享:减少数据复制,降低内存占用
  • 高性能访问:提供快速的数据读取能力
  • 数据一致性:保证多服务间的数据同步
  • 易用性:提供简单的 API 接口

三种机制对比

机制 特点 适用场景 性能 更新方式
sharedata 只读,内存共享,版本管理 配置文件、静态数据 极高 全量更新
sharetable 只读,虚拟机共享,零复制 大表数据、模板数据 极高 全量更新
datacenter 可读写,服务间共享 动态数据、会话管理 中等 增量更新

模块位置

  • sharedata: lualib/skynet/sharedata.lua + service/sharedatad.lua + lualib-src/lua-sharedata.c
  • sharetable: lualib/skynet/sharetable.lua + lualib-src/lua-sharetable.c
  • datacenter: lualib/skynet/datacenter.lua + service/datacenterd.lua

sharedata 完整流程

创建 sharedatad 服务

服务启动流程

require "skynet.sharedata" 时,创建 sharedatad 服务

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      sharedatad 服务创建流程                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. 服务启动                                                                 │
│     skynet.uniqueservice "sharedatad"                                       │
│     ↓                                                                       │
│                                                                             │
│  2. 初始化全局数据结构                                                        │
│     local pool = {}          -- 共享数据池: name -> {obj, watch}             │
│     local pool_count = {}    -- 计数器: name -> {n, threshold}               │
│     local objmap = {}        -- 对象映射: cobj -> v 或 true                  │
│     ↓                                                                       │
│                                                                             │
│  3. 启动垃圾回收协程                                                          │
│     skynet.fork(collectobj)                                                 │
│     ↓                                                                       │
│                                                                             │
│  4. 注册消息处理函数                                                          │
│     skynet.dispatch("lua", function(session, source, cmd, ...)              │
│         local f = CMD[cmd]                                                  │
│         local r = f(...)                                                    │
│         if r ~= NORET then                                                  │
│             skynet.ret(skynet.pack(r))                                      │
│         end                                                                 │
│     end)                                                                    │
│     ↓                                                                       │
│                                                                             │
│  5. 服务就绪,等待客户端请求                                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
核心数据结构
lua 复制代码
-- sharedatad.lua 全局变量

-- 共享数据池:存储所有共享数据
-- 结构: pool[name] = {obj = cobj, watch = {...}}
local pool = {}

-- 监控计数器:优化监控队列检查
-- 结构: pool_count[name] = {n = 当前监控数, threshold = 检查阈值}
local pool_count = {}

-- 对象映射:跟踪所有共享对象的生命周期
-- 结构: objmap[cobj] = v (正在使用) 或 true (待回收)
local objmap = {}

-- 垃圾回收计数器
local collect_tick = 10  -- 每 10 分钟执行一次 GC
服务接口
lua 复制代码
local CMD = {}

-- 创建共享数据
function CMD.new(name, t, ...)
    -- 支持三种方式:
    -- 1. 直接传入表: new("config", {...})
    -- 2. 加载文件: new("config", "@/path/to/config.lua")
    -- 3. 执行代码: new("config", "return {...}")
end

-- 查询共享数据
function CMD.query(name)
    -- 返回共享对象指针
end

-- 更新共享数据
function CMD.update(name, t, ...)
    -- 创建新版本并通知所有监控者
end

-- 删除共享数据
function CMD.delete(name)
    -- 标记对象待回收
end

-- 监控数据更新
function CMD.monitor(name, obj)
    -- 如果数据有更新,返回新对象;否则挂起协程
end

-- 确认引用
function CMD.confirm(cobj)
    -- 减少引用计数
end

sharedata.new() 流程

完整调用流程
复制代码
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                      sharedata.new(name, table) 流程                                │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                     │
│  客户端服务                     sharedatad 服务              C 模块                   │
│      │                              │                         │                     │
│      │ sharedata.new("config", t)   │                         │                     │
│      ├─────────────────────────────►│                         │                     │
│      │                              │                         │                     │
│      │                              │ CMD.new("config", t)    │                     │
│      │                              │   ↓                     │                     │
│      │                              │                         │                     │
│      │                              │ 检查是否已存在           │                     │
│      │                              │  assert(pool[name]==nil)│                     │
│      │                              │                         │                     │
│      │                              │                         │                     │
│      │                              │ 判断参数类型             │                     │
│      │                              │   if type(t) == "table" │                     │
│      │                              │       value = t         │                     │
│      │                              │   elseif type(t) == "string"                  │
│      │                              │       加载文件或代码     │                     │
│      │                              │                         │                     │
│      │                              │                         │                     │
│      │                              │ newobj(name, value)     │                     │
│      │                              │   ↓                     │                     │
│      │                              │                         │                     │
│      │                              │ sharedata.host.new(tbl) │                     │
│      │                              ├────────────────────────►│                     │
│      │                              │                         │                     │
│      │                              │                         │ 创建独立 Lua 状态机  │
│      │                              │                         │                     │
│      │                              │                         │ L = luaL_newstate() │
│      │                              │                         │                     │
│      │                              │                         │ 转换表结构           │
│      │                              │                         │ convtable(L, tbl)   │
│      │                              │                         │                     │
│      │                              │                         │    返回指针          │
│      │                              │                         │                     │
│      │                              │  ◄── cobj ───────────── │                     │
│      │                              │                         │                     │
│      │                              │ 增加引用计数             │                     │
│      │                              │   sharedata.host.       │                     │
│      │                              │   incref(cobj)          │                     │
│      │                              ├────────────────────────►│                     │
│      │                              │                         │                     │
│      │                              │ 创建共享对象记录          │                     │
│      │                              │ v = {obj=cobj, watch={}}│                     │
│      │                              │                         │                     │
│      │                              │   objmap[cobj] = v      │                     │
│      │                              │   pool[name] = v        │                     │
│      │                              │   pool_count[name] =    │                     │
│      │                              │    {n=0, threshold=16}  │                     │
│      │                              │                         │                     │
│      │  ◄── 创建成功 ─────────────   │                         │                     │
│      │                              │                         │                     │
└─────────────────────────────────────────────────────────────────────────────────────┘

sharedata.query() 流程

完整调用流程
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      sharedata.query(name) 流程                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  客户端服务                     sharedatad 服务              C 模块           │
│      │                              │                         │             │
│      │ sharedata.query("config")    │                         │             │
│      │   ↓                          │                         │             │
│      │                              │                         │             │
│      │ 检查缓存                      │                         │             │
│      │   if cache[name] then        │                         │             │
│      │       return cache[name]     │                         │             │
│      │   end                        │                         │             │
│      │                              │                         │             │
│      │ skynet.call(service, "lua",  │                         │             │
│      │             "query", name)   │                         │             │
│      ├─────────────────────────────►│                         │             │
│      │                              │                         │             │
│      │                              │ CMD.query(name)         │             │
│      │                              │   ↓                     │             │
│      │                              │                         │             │
│      │                              │ 获取共享对象             │             │
│      │                              │   v = pool[name]        │             │
│      │                              │   obj = v.obj           │             │
│      │                              │                         │             │
│      │                              │ 增加引用计数             │             │
│      │                              │   sharedata.host.       │             │
│      │                              │   incref(obj)           │             │
│      │                              ├────────────────────────►│             │
│      │                              │                         │             │
│      │  ◄── cobj ─────────────────  │                         │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │ 检查缓存                      │                         │             │
│      │   if cache[name] and         │                         │             │
│      │      cache[name].__obj==cobj │                         │             │
│      │   then                       │                         │             │
│      │       确认引用                │                         │             │
│      │       send(service, "confirm",cobj)                    │             │
│      │       return cache[name]     │                         │             │
│      │   end                        │                         │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │ 创建共享对象盒子              │                         │             │
│      │   r = sd.box(cobj)           │                         │             │
│      ├─────────────────────────────────────────────────────►  │             │
│      │                              │                         │             │
│      │                              │                         │ 创建 ctrl   │
│      │                              │                         │ 结构        │
│      │                              │                         │   c->root   │
│      │                              │                         │   = cobj    │
│      │  ◄── r (userdata) ──────────────────────────────────   │             │
│      │                              │                         │             │
│      │ 确认引用                      │                         │             │
│      │   send(service, "confirm", cobj)                       │             │
│      ├─────────────────────────────►│                         │             │
│      │                              │                         │             │
│      │ 启动监控协程                  │                         │             │
│      │   skynet.fork(monitor,       │                         │             │
│      │                name, r, cobj)│                         │             │
│      │                              │                         │             │
│      │ 缓存对象                      │                         │             │
│      │   cache[name] = r            │                         │             │
│      │                              │                         │             │
│      │ return r                     │                         │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │ 监控协程(异步执行)           │                         │             │
│      │   while true do              │                         │             │
│      │       newobj = call(service, │                         │             │
│      │                   "monitor", │                         │             │
│      │                   name, cobj)│                         │             │
│      ├─────────────────────────────►│                         │             │
│      │                              │                         │             │
│      │                              │ 检查是否有更新           │             │
│      │                              │   if obj != pool[name].obj            │
│      │                              │   then                  │             │
│      │                              │       incref(newobj)    │             │
│      │  ◄── newobj ──────────────── │       return newobj     │             │
│      │                              │   end                   │             │
│      │                              │                         │             │
│      │                              │ 加入监控队列             │             │
│      │                              │   table.insert(v.watch, │             │
│      │                              │       skynet.response())│             │
│      │                              │                         │             │
│      │  (协程挂起,等待更新通知)    │                         │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │ if newobj == nil then        │                         │             │
│      │     break  -- 数据已删除      │                         │             │
│      │ end                          │                         │             │
│      │                              │                         │             │
│      │ 更新本地对象                  │                         │             │
│      │   sd.update(obj, newobj)     │                         │             │
│      ├─────────────────────────────────────────────────────►  │             │
│      │                              │                         │ 更新引用     │
│      │                              │                         │   c->root   │
│      │                              │                         │   = newobj  │
│      │  ◄── 更新完成 ──────────────────────────────────────    │             │
│      │                              │                         │             │
│      │ 确认更新                      │                         │             │
│      │   send(service, "confirm", newobj)                     │             │
│      ├─────────────────────────────►│                         │             │
│      │                              │                         │             │
│      │ end  -- 继续监控              │                         │             │
│      │                              │                         │             │
└─────────────────────────────────────────────────────────────────────────────┘

sharedata.update() 流程

完整调用流程
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      sharedata.update(name, new_table) 流程                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  客户端服务                     sharedatad 服务              其他服务         │
│      │                              │                         │             │
│      │ sharedata.update("config",   │                         │             │
│      │                 new_config)  │                         │             │
│      ├─────────────────────────────►│                         │             │
│      │                              │                         │             │
│      │                              │ CMD.update(name, t)     │             │
│      │                              │   ↓                     │             │
│      │                              │                         │             │
│      │                              │ 获取旧对象               │             │
│      │                              │   v = pool[name]        │             │
│      │                              │   oldcobj = v.obj       │             │
│      │                              │   watch = v.watch       │             │
│      │                              │                         │             │
│      │                              │ 标记旧对象待回收          │            │
│      │                              │   objmap[oldcobj] = true│             │
│      │                              │   sharedata.host.       │             │
│      │                              │   decref(oldcobj)       │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │                              │ 创建新对象               │             │
│      │                              │   CMD.new(name, t)      │             │
│      │                              │   newobj = pool[name].obj│            │
│      │                              │                         │             │
│      │                              │                         │             │
│      │                              │ 标记旧对象为 dirty       │             │
│      │                              │   sharedata.host.       │             │
│      │                              │   markdirty(oldcobj)    │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │                              │ 通知所有监控者           │             │
│      │                              │   for _, response in    │             │
│      │                              │       pairs(watch) do   │             │
│      │                              │       incref(newobj)    │             │
│      │                              │       response(true, newobj)          │
│      │                              │   end                   │             │
│      │                              │                         │             │
│      │                              │         ┌───────────────┤             │
│      │                              │         │ 唤醒监控协程   │             │
│      │                              │         │   newobj =    │             │
│      │                              │         │   call(...)   │             │
│      │                              │         │   返回 newobj │             │
│      │                              │         │               │             │
│      │                              │         │ sd.update(obj,│             │
│      │                              │         │     newobj)   │             │
│      │                              │         │               │             │
│      │                              │         │ send("confirm"│             │
│      │                              │         │     , newobj) │             │
│      │                              │         └───────────────┤             │
│      │                              │                         │             │
│      │                              │ 触发垃圾回收             │             │
│      │                              │   collect1min()         │             │
│      │                              │                         │             │
│      │  ◄── 更新成功 ─────────────   │                         │             │
│      │                              │                         │             │
│      │                              │                         │             │
│      │                              │ 1分钟后,GC 协程执行      │             │
│      │                              │   collectgarbage()      │             │
│      │                              │   for obj, v in pairs(objmap) do      │
│      │                              │       if v == true then │             │
│      │                              │           if getref(obj)<=0 then      │
│      │                              │               delete(obj)│            │
│      │                              │           end           │             │
│      │                              │       end               │             │
│      │                              │   end                   │             │
│      │                              │                         │             │
└─────────────────────────────────────────────────────────────────────────────┘

cobj 和共享对象盒子详解

核心概念

什么是 cobj?

cobj 是一个 C 指针,指向 struct table 结构,它代表了一个在独立 Lua 状态机中创建的共享数据对象。

c 复制代码
// cobj 的类型
struct table * cobj;

// cobj 指向的结构
struct table {
    int sizearray;          /* 数组部分大小 */
    int sizehash;           /* 哈希部分大小 */
    uint8_t *arraytype;     /* 数组部分类型数组 */
    union value * array;    /* 数组部分值数组 */
    struct node * hash;     /* 哈希部分节点数组 */
    lua_State * L;          /* 关联的独立 Lua 状态机 */
};

cobj 的特点

  • 真正的共享内存:所有服务访问同一份数据,零复制
  • 独立 Lua 状态机:存储在独立的状态机中,避免污染主状态机
  • 只读访问:数据一旦创建,不可修改(除非整体更新)
  • 引用计数管理:自动跟踪使用情况,智能垃圾回收
什么是共享对象盒子?

共享对象盒子是一个 Lua userdata,包装了 cobj 指针,提供了 Lua 层的访问接口。

c 复制代码
// 共享对象盒子的类型
struct ctrl {
    struct table * root;    /* 当前使用的表指针 (cobj) */
    struct table * update;  /* 新表指针(更新时使用) */
};

共享对象盒子的作用

  • 提供 Lua 访问接口 :通过元表实现 __index__pairs
  • 跟踪对象引用:记录哪些服务在使用这个对象
  • 支持更新机制:当数据更新时,可以无缝切换到新版本
  • 实现只读访问:防止客户端修改共享数据

为什么需要这样的设计?

问题 1:如何实现真正的内存共享?

传统方式的问题

lua 复制代码
-- ❌ 错误方式:直接传递 Lua 表
local config = {version = "1.0"}
skynet.call(service, "lua", "get_config")
-- 问题:每次都要序列化/反序列化,内存复制开销大

sharedata 的解决方案

lua 复制代码
-- ✅ 正确方式:共享内存
local cobj = sharedata.host.new(tbl)  -- 创建 cobj
local box = sd.box(cobj)               -- 创建盒子

-- 所有服务访问同一个 cobj,零复制

内存布局

lua 复制代码
local config = {
    version = "1.0",
    server = {
        host = "127.0.0.1",
        port = 8888,
    },
}
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                          内存布局示意图                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  sharedatad 服务(独立 Lua 状态机)                                           │
│  ┌─────────────────────────────────────────────────────────────┐            │
│  │  Lua State (独立状态机)                                      │            │
│  │  ┌─────────────────────────────────────────────────────┐    │            │
│  │  │  字符串表                                            │    │            │
│  │  │  [1] = "version"                                    │    │            │
│  │  │  [2] = "server"                                     │    │            │
│  │  │  [3] = "host"                                       │    │            │
│  │  └─────────────────────────────────────────────────────┘    │            │
│  └─────────────────────────────────────────────────────────────┘            │
│                                                                             │
│  共享内存区域(struct table)                                                │
│  ┌─────────────────────────────────────────────────────────────┐            │
│  │  struct table * cobj (指针地址: 0x12345678)                  │            │
│  │  ┌─────────────────────────────────────────────────────┐    │            │
│  │  │  数组部分                                            │    │            │
│  │  │  [0]: type = NIL                                    │    │            │
│  │  └─────────────────────────────────────────────────────┘    │            │
│  │  ┌─────────────────────────────────────────────────────┐    │            │
│  │  │  哈希部分                                            │    │            │
│  │  │  ["version"]: type = STRING, value = 1              │    │            │
│  │  │  ["server"]: type = TABLE, value = cobj2            │    │            │
│  │  └─────────────────────────────────────────────────────┘    │            │
│  └─────────────────────────────────────────────────────────────┘            │
│                                                                             │
│  客户端服务 A                    客户端服务 B                                 │
│  ┌─────────────────────┐        ┌─────────────────────┐                     │
│  │  ctrl (userdata)    │        │  ctrl (userdata)    │                     │
│  │  root = 0x12345678  │        │  root = 0x12345678  │                     │
│  │  update = NULL      │        │  update = NULL      │                     │
│  └─────────────────────┘        └─────────────────────┘                     │
│           │                              │                                  │
│           └──────────┬───────────────────┘                                  │
│                      │                                                      │
│                      ▼                                                      │
│              都指向同一个 cobj                                               │
│              (0x12345678)                                                   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
问题 2:如何跟踪对象的使用情况?

引用计数机制

c 复制代码
// state 结构中的引用计数
struct state {
    int dirty;              /* 更新标记 */
    ATOM_INT ref;           /* 原子引用计数 */
    struct table * root;    /* 根表指针 */
};

引用计数流程

复制代码
创建 cobj
  ↓
state.ref = 0

服务 A 查询
  ↓
创建 ctrl (box)
  ↓
state.ref++ (ref = 1)

服务 B 查询
  ↓
创建 ctrl (box)
  ↓
state.ref++ (ref = 2)

服务 A 退出
  ↓
ctrl 垃圾回收
  ↓
state.ref-- (ref = 1)

更新数据
  ↓
创建新 cobj2
  ↓
服务 A 的 ctrl 更新: root = cobj2
  ↓
旧 cobj.ref-- (ref = 0)
  ↓
垃圾回收旧 cobj
问题 3:如何实现无缝更新?

盒子机制的优势

lua 复制代码
-- 客户端服务
local config = sharedata.query("config")  -- 获取盒子

-- 访问数据(通过盒子)
print(config.version)  -- 访问旧版本

-- sharedatad 服务更新数据
sharedata.update("config", new_config)

-- 监控协程收到通知
  ↓
sd.update(config, new_cobj)  -- 更新盒子的 root 指针
  ↓
config.root = new_cobj

-- 客户端继续使用同一个盒子
print(config.version)  -- 自动访问新版本!

关键点

  • 客户端持有的是盒子(ctrl),不是 cobj
  • 盒子的 root 指针可以动态更新
  • 客户端无感知,自动使用新版本

详细数据流转

创建 cobj 的完整过程
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                          创建 cobj 流程                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  步骤 1: 客户端调用                                                          │
│  sharedata.new("config", tbl)                                               │
│                                                                             │
│  步骤 2: sharedatad 服务处理                                                 │
│  CMD.new("config", tbl)                                                     │
│    ↓                                                                        │
│  newobj(name, tbl)                                                          │
│    ↓                                                                        │
│  sharedata.host.new(tbl)  ─────► C 层: lnewconf()                           │
│                                                                             │
│  步骤 3: C 层创建                                                            │
│  ┌──────────────────────────────────────────────────────────────┐           │
│  │  1. 创建独立 Lua 状态机                                       │           │
│  │     ctx.L = luaL_newstate()                                  │           │
│  │                                                              │           │
│  │  2. 分配 table 结构                                           │           │
│  │     tbl = malloc(sizeof(struct table))                       │           │
│  │     tbl->L = ctx.L  // 关联状态机                             │           │
│  │                                                              │           │
│  │  3. 转换 Lua 表为内部格式                                     │           │
│  │     convtable(L, tbl)                                        │           │
│  │       - 分配数组部分: arraytype[], array[]                    │           │
│  │       - 分配哈希部分: hash[]                                  │           │
│  │       - 填充数据值                                            │           │
│  │       - 字符串存储到独立状态机                                 │           │
│  │                                                              │           │
│  │  4. 创建 state 结构                                           │           │
│  │     s = lua_newuserdatauv(L, sizeof(*s), 1)                  │           │
│  │     s->dirty = 0                                             │           │
│  │     s->ref = 0     // 引用计数初始化为 0                      │           │
│  │     s->root = tbl  // 指向 table                             │           │
│  │                                                              │           │
│  │  5. 返回 cobj                                                │           │
│  │     lua_pushlightuserdata(L, tbl)  // 返回指针                │           │
│  └──────────────────────────────────────────────────────────────┘           │
│                                                                             │
│  步骤 4: sharedatad 记录                                                     │
│  ┌──────────────────────────────────────────────────────────────┐           │
│  │  local cobj = sharedata.host.new(tbl)                        │           │
│  │                                                              │           │
│  │  // 增加引用计数(初始引用)                                   │           │
│  │  sharedata.host.incref(cobj)  // ref = 1                     │           │
│  │                                                              │           │
│  │  // 创建共享对象记录                                           │           │
│  │  local v = {                                                 │           │
│  │      obj = cobj,      // 保存 cobj 指针                       │           │
│  │      watch = {}       // 监控队列                             │           │
│  │  }                                                           │           │
│  │                                                              │           │
│  │  // 记录到映射表                                              │           │
│  │  objmap[cobj] = v                                            │           │
│  │  pool["config"] = v                                          │           │
│  └──────────────────────────────────────────────────────────────┘           │
│                                                                             │
│  结果: cobj 创建完成,存储在独立 Lua 状态机中                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

cobj 的内存布局

c 复制代码
// cobj 指向的完整结构
struct table * cobj = 0x12345678;

// 内存布局
地址 0x12345678: struct table {
    sizearray = 2,
    sizehash = 1,
    
    // 数组部分
    arraytype = 0xAAAA0000 [
        [0] = VALUETYPE_NIL,
        [1] = VALUETYPE_TABLE
    ],
    array = 0xAAAA1000 [
        [0] = {...},
        [1] = {.tbl = 0x12345690}  // 嵌套表指针
    ],
    
    // 哈希部分
    hash = 0xBBBB0000 [
        [0] = {
            key = 1,              // 字符串索引
            keytype = KEYTYPE_STRING,
            keyhash = 0x87654321,
            valuetype = VALUETYPE_STRING,
            v = {.string = 1},   // 字符串索引
            next = -1,           // 无冲突
            nocolliding = 1
        }
    ],
    
    // 关联的独立 Lua 状态机
    L = 0xCCCC0000  // 独立状态机,存储字符串等
}
创建共享对象盒子的完整过程
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        创建共享对象盒子流程                                   │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  步骤 1: 客户端查询                                                          │
│  sharedata.query("config")                                                  │
│                                                                             │
│  步骤 2: 获取 cobj                                                           │
│  local cobj = skynet.call(service, "lua", "query", "config")                │
│    ↓                                                                        │
│  sharedatad: CMD.query("config")                                            │
│    ↓                                                                        │
│  sharedata.host.incref(cobj)  // 增加引用计数                                │
│    ↓                                                                        │
│  return cobj  // 返回 lightuserdata                                         │
│                                                                             │
│  步骤 3: 创建盒子                                                            │
│  local r = sd.box(cobj)  ─────► C 层: lboxconf()                            │
│                                                                             │
│  步骤 4: C 层创建 ctrl                                                       │
│  ┌──────────────────────────────────────────────────────────────┐           │
│  │  1. 获取 state 结构                                           │           │
│  │     struct state * s = lua_touserdata(cobj->L, 1)            │           │
│  │                                                              │           │
│  │  2. 增加引用计数                                              │           │
│  │     ATOM_FINC(&s->ref)  // ref++                             │           │
│  │                                                              │           │
│  │  3. 创建 ctrl (userdata)                                     │           │
│  │     struct ctrl * c = lua_newuserdatauv(L, sizeof(*c), 1)    │           │
│  │     c->root = cobj   // 指向共享数据                          │           │
│  │     c->update = NULL // 无更新                                │           │
│  │                                                              │           │
│  │  4. 设置元表                                                  │           │
│  │     luaL_newmetatable(L, "confctrl")                         │           │
│  │       - __index: 读取访问                                     │           │
│  │       - __pairs: 遍历支持                                     │           │
│  │       - __len: 获取长度                                       │           │
│  │       - __gc: 垃圾回收                                        │           │
│  │     lua_setmetatable(L, -2)                                  │           │
│  │                                                              │           │
│  │  5. 返回 ctrl (userdata)                                     │            │
│  └──────────────────────────────────────────────────────────────┘           │
│                                                                             │
│  步骤 5: 启动监控协程                                                         │
│  skynet.fork(monitor, "config", r, cobj)                                    │
│                                                                             │
│  结果: 盒子创建完成,可以访问共享数据                                          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

ctrl 的内存布局

lua 复制代码
-- ctrl 是一个 Lua userdata
local ctrl = {
    -- C 层数据(不可见)
    root = 0x12345678,    -- 指向 cobj
    update = NULL,        -- 指向新 cobj(更新时使用)
    
    -- 元表
    __index = function(self, key)
        -- 从 cobj 中查找 key
        local cobj = self.root
        return lookup_from_cobj(cobj, key)
    end,
    
    __pairs = function(self)
        -- 遍历 cobj
        return pairs_iterator, self, nil
    end,
    
    __gc = function(self)
        -- 减少引用计数
        local cobj = self.root
        decref(cobj)
    end
}

访问共享数据的完整流程

读取字段
lua 复制代码
-- 客户端代码
local config = sharedata.query("config")
local version = config.version

底层流程

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        config.version 访问流程                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. Lua 层访问                                                               │
│     config.version                                                          │
│       ↓                                                                     │
│     触发 __index 元方法                                                      │
│                                                                             │
│  2. __index 实现 (C 层)                                                     │
│     static int lindex(lua_State *L) {                                       │
│         // 获取 ctrl 结构                                                    │
│         struct ctrl * c = lua_touserdata(L, 1);                             │
│         struct table * cobj = c->root;                                      │
│                                                                             │
│         // 获取键 "version"                                                  │
│         const char * key = lua_tostring(L, 2);                              │
│         size_t sz = strlen(key);                                            │
│                                                                             │
│         // 计算哈希值                                                        │
│         uint32_t keyhash = calchash(key, sz);                               │
│                                                                             │
│         // 在 cobj 的哈希表中查找                                             │
│         struct node * n = lookup_key(cobj, keyhash, ...);                   │
│                                                                             │
│         // 获取值                                                            │
│         if (n->valuetype == VALUETYPE_STRING) {                             │
│             // 从独立 Lua 状态机获取字符串                                    │
│             const char * str = lua_tolstring(cobj->L, n->v.string, &sz);    │
│             lua_pushlstring(L, str, sz);                                    │
│         }                                                                   │
│                                                                             │
│         return 1;  // 返回值                                                 │
│     }                                                                       │
│                                                                             │
│  3. 返回值到 Lua 层                                                          │
│     version = "1.0"                                                         │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
访问嵌套表
lua 复制代码
-- 客户端代码
local config = sharedata.query("config")
local server = config.server  -- 获取嵌套表
local host = server.host      -- 访问嵌套表的字段

底层流程

复制代码
config.server
  ↓
__index("server")
  ↓
lookup_key(cobj, "server")
  ↓
找到节点: n->valuetype = VALUETYPE_TABLE
  ↓
返回 n->v.tbl (嵌套表的 cobj 指针)
  ↓
返回 lightuserdata (0x12345690)
  ↓
Lua 层收到 lightuserdata
  ↓
不能直接使用!需要创建新的盒子
  ↓
实际上,__index 会自动创建子盒子
  ↓
返回子盒子(包含嵌套表指针)
  ↓
server.host
  ↓
访问子盒子的 __index
  ↓
lookup_key(嵌套表cobj, "host")
  ↓
返回 "127.0.0.1"

引用计数管理

引用计数的生命周期
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        引用计数生命周期                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  创建 cobj                                                                   │
│  sharedata.new("config", tbl)                                               │
│    ↓                                                                        │
│  cobj 创建,state.ref = 0                                                   │
│    ↓                                                                        │
│  sharedatad 持有引用:incref(cobj)                                           │
│    ↓                                                                        │
│  state.ref = 1  ─────────────────────────────────────────┐                  │
│                                                          │                  │
│  服务 A 查询                                              │                  │
│  sharedata.query("config")                               │                  │
│    ↓                                                     │                  │
│  创建 ctrl_A: incref(cobj)                               │                  │
│    ↓                                                     │                  │
│  state.ref = 2  ◄────────────────────────────────────────┘                  │
│                                                                             │
│  服务 B 查询                                                                 │
│  sharedata.query("config")                                                  │
│    ↓                                                                        │
│  创建 ctrl_B: incref(cobj)                                                  │
│    ↓                                                                        │
│  state.ref = 3                                                              │
│                                                                             │
│  更新数据                                                                    │
│  sharedata.update("config", new_tbl)                                        │
│    ↓                                                                        │
│  创建新 cobj2,state2.ref = 1                                                │
│    ↓                                                                        │
│  标记旧 cobj 为 dirty                                                        │
│    ↓                                                                        │
│  通知监控者                                                                  │
│    ↓                                                                        │
│  服务 A 的监控协程收到通知                                                    │
│    ↓                                                                        │
│  sd.update(ctrl_A, cobj2)                                                   │
│    ↓                                                                        │
│  ctrl_A->root = cobj2                                                       │
│  incref(cobj2)  // state2.ref = 2                                           │
│  decref(cobj)   // state.ref = 2                                            │
│    ↓                                                                        │
│  服务 B 的监控协程收到通知                                                    │
│    ↓                                                                        │
│  sd.update(ctrl_B, cobj2)                                                   │
│    ↓                                                                        │
│  ctrl_B->root = cobj2                                                       │
│  incref(cobj2)  // state2.ref = 3                                           │
│  decref(cobj)   // state.ref = 1                                            │
│                                                                             │
│  服务 A 退出                                                                 │
│    ↓                                                                        │
│  ctrl_A 垃圾回收                                                             │
│    ↓                                                                        │
│  __gc: decref(cobj2)  // state2.ref = 2                                     │
│                                                                             │
│  服务 B 退出                                                                 │
│    ↓                                                                        │
│  ctrl_B 垃圾回收                                                             │
│    ↓                                                                        │
│  __gc: decref(cobj2)  // state2.ref = 1                                     │
│                                                                             │
│  垃圾回收(1分钟后)                                                          │
│    ↓                                                                        │
│  collectgarbage()                                                           │
│    ↓                                                                        │
│  检查旧 cobj: state.ref = 1 (sharedatad 持有)                                │
│    ↓                                                                        │
│  decref(cobj)  // state.ref = 0                                             │
│    ↓                                                                        │
│  state.ref = 0,删除 cobj                                                    │
│    ↓                                                                        │
│  sharedata.host.delete(cobj)                                                │
│    ↓                                                                        │
│  lua_close(cobj->L)  // 关闭独立状态机                                       │
│  delete_tbl(cobj)    // 释放内存                                             │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

cobj 和盒子的关系

关系图
复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                      cobj 和盒子的关系                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                    sharedatad 服务                                          │
│                    ┌───────────────┐                                        │
│                    │ pool["config"]│                                        │
│                    │   .obj = cobj │                                        │
│                    │   .watch = [] │                                        │
│                    └───────┬───────┘                                        │
│                            │                                                │
│                            │ 持有                                           │
│                            ▼                                                │
│                     ┌─────────────┐                                         │
│                     │    cobj     │ ◄──────────────────┐                    │
│                     │ (struct     │                    │                    │
│                     │  table*)    │                    │ 真正的共享数据      │
│                     │             │                    │ (独立 Lua 状态机)   │
│                     │ ref = 3     │                    │                    │
│                     └──────┬──────┘                    │                    │
│                            │                           │                    │
│          ┌─────────────────┼─────────────────┐         │                    │
│          │                 │                 │         │                    │
│          ▼                 ▼                 ▼         │                    │
│     ┌─────────┐      ┌─────────┐      ┌─────────┐      │                    │
│     │ ctrl_A  │      │ ctrl_B  │      │ ctrl_C  │      │                    │
│     │ (box)   │      │ (box)   │      │ (box)   │      │                    │
│     ├─────────┤      ├─────────┤      ├─────────┤      │                    │
│     │root=cobj│      │root=cobj│      │root=cobj│      │                    │
│     │update=  │      │update=  │      │update=  │      │                    │
│     │ NULL    │      │ NULL    │      │ NULL    │      │                    │
│     └─────────┘      └─────────┘      └─────────┘      │                    │
│          ▲                 ▲                 ▲         │                    │
│          │                 │                 │         │                    │
│     服务 A 持有       服务 B 持有       服务 C 持有      │                    │
│                                                        │                    │
│     通过 ctrl 访问 cobj(所有服务共享同一份数据)─────────┘                    │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
职责划分
组件 职责 位置
cobj 存储实际的共享数据 独立 Lua 状态机
state 管理引用计数和更新状态 独立 Lua 状态机
ctrl 提供 Lua 访问接口 客户端服务
pool 管理所有共享数据 sharedatad 服务
objmap 跟踪对象生命周期 sharedatad 服务

设计优势

1. 真正的内存共享
lua 复制代码
-- 传统方式:每个服务都有一份拷贝
local config1 = load_config()  -- 服务 A 的拷贝
local config2 = load_config()  -- 服务 B 的拷贝
-- 内存占用:N * sizeof(config)

-- ShareData:所有服务共享一份数据
local config1 = sharedata.query("config")  -- 服务 A 获取盒子
local config2 = sharedata.query("config")  -- 服务 B 获取盒子
-- 内存占用:sizeof(config) + N * sizeof(ctrl)
// ctrl 很小,只有两个指针
2. 自动更新通知
lua 复制代码
-- 客户端无需主动轮询
local config = sharedata.query("config")

-- 监控协程自动运行
-- 收到更新通知时,自动更新盒子的 root 指针
-- 客户端继续使用同一个盒子,自动访问新数据
print(config.version)  -- 自动访问最新版本
3. 引用计数自动管理
lua 复制代码
-- 客户端无需手动管理引用计数
local config = sharedata.query("config")

-- 使用完毕后,盒子被垃圾回收
-- __gc 元方法自动减少引用计数
config = nil
collectgarbage()  -- 触发 __gc,自动 decref
4. 线程安全
c 复制代码
// 使用原子操作保证线程安全
ATOM_FINC(&s->ref);  // 原子递增
ATOM_FDEC(&s->ref);  // 原子递减
ATOM_LOAD(&s->ref);  // 原子读取

总结

cobj 的作用
  1. 存储共享数据:在独立 Lua 状态机中存储实际的共享数据
  2. 零复制共享:所有服务访问同一份数据,减少内存占用
  3. 类型安全:C 层严格管理数据类型,保证数据一致性
  4. 生命周期管理:通过引用计数自动管理生命周期
共享对象盒子的作用
  1. 提供 Lua 接口:将 cobj 包装为 Lua userdata,提供元表访问
  2. 跟踪引用:每个盒子代表一个服务的引用
  3. 支持更新:盒子的 root 指针可以动态更新
  4. 自动垃圾回收:通过 __gc 元方法自动管理引用计数
两者的关系
复制代码
cobj (数据) ←─── 盒子 (接口)
  │                │
  │                │
  ▼                ▼
存储实际数据    提供 Lua 访问
独立状态机      客户端服务
引用计数        跟踪引用
可更新          无感知更新
核心优势
  • 内存效率:零复制共享,大幅减少内存占用
  • 性能优越:直接访问内存,无序列化开销
  • 自动管理:引用计数自动管理,无内存泄漏
  • 线程安全:原子操作保证并发安全
  • 无缝更新:客户端无感知的数据更新

sharedata 核心实现

Lua 层实现

参见 sharedata.lua 文件的详细注释。项目地址

C 层实现

参见 lua-sharedata.c 文件的详细注释。项目地址


数据结构

核心数据结构
c 复制代码
/* 值类型 */
#define VALUETYPE_NIL     0   /* nil 值 */
#define VALUETYPE_REAL    1   /* 浮点数 */
#define VALUETYPE_STRING  2   /* 字符串 */
#define VALUETYPE_BOOLEAN 3   /* 布尔值 */
#define VALUETYPE_TABLE   4   /* 表 */
#define VALUETYPE_INTEGER 5   /* 整数 */

/* 键类型 */
#define KEYTYPE_INTEGER 0     /* 整数键 */
#define KEYTYPE_STRING  1     /* 字符串键 */

/**
 * 值联合体
 * 用于存储不同类型的值,节省内存空间
 */
union value {
    lua_Number n;           /* 浮点数值 */
    lua_Integer d;          /* 整数值 */
    struct table * tbl;     /* 嵌套表指针 */
    int string;             /* 字符串索引 */
    int boolean;            /* 布尔值 */
};

/**
 * 哈希节点
 * 存储哈希表中的键值对
 */
struct node {
    union value v;          /* 值 */
    int key;                /* 键:整数或字符串索引 */
    int next;               /* 冲突链下一个节点索引 */
    uint32_t keyhash;       /* 键的哈希值 */
    uint8_t keytype;        /* 键类型 */
    uint8_t valuetype;      /* 值类型 */
    uint8_t nocolliding;    /* 是否无哈希冲突 */
};

/**
 * 状态结构
 * 管理共享数据的状态信息
 */
struct state {
    int dirty;              /* 更新标记 */
    ATOM_INT ref;           /* 原子引用计数 */
    struct table * root;    /* 根表指针 */
};

/**
 * 数据表结构
 * 存储共享数据的完整结构
 */
struct table {
    int sizearray;          /* 数组部分大小 */
    int sizehash;           /* 哈希部分大小 */
    uint8_t *arraytype;     /* 数组部分类型数组 */
    union value * array;    /* 数组部分值数组 */
    struct node * hash;     /* 哈希部分节点数组 */
    lua_State * L;          /* 关联的独立 Lua 状态机 */
};

/**
 * 控制结构
 * 用于客户端服务跟踪共享数据
 */
struct ctrl {
    struct table * root;    /* 当前使用的表指针 */
    struct table * update;  /* 新表指针 */
};

sharetable

此处不展开


datacenter

此处不展开


对比与选择

详细对比

特性 sharedata sharetable datacenter
数据类型 只读表 只读表 任意类型
访问方式 共享内存 虚拟机共享 服务调用
更新方式 全量更新 全量更新 增量更新
性能 极高 极高 中等
内存占用 最低 中等
适用场景 配置文件 大表数据 动态数据
并发安全
等待机制
更新通知 自动推送 手动触发

最佳实践

参考 共享数据项目级应用.md


总结

sharedata 核心流程

  1. 创建服务skynet.uniqueservice "sharedatad"
  2. 创建数据sharedata.new(name, table) -> C 层创建独立 Lua 状态机 -> 转换表结构
  3. 查询数据sharedata.query(name) -> 返回共享对象盒子 -> 启动监控协程
  4. 更新数据sharedata.update(name, new_table) -> 创建新版本 -> 通知所有监控者

关键设计

  • 独立 Lua 状态机:每个共享数据在独立的状态机中,避免污染
  • 引用计数:自动跟踪对象引用,智能垃圾回收
  • 监控机制:自动通知客户端数据更新
  • 弱引用缓存:客户端缓存,减少重复查询

参考文件

  • skynet/lualib/skynet/sharedata.lua - sharedata 客户端
  • skynet/service/sharedatad.lua - sharedata 服务
  • skynet/lualib-src/lua-sharedata.c - sharedata C 层
  • https://gitee.com/jiucows/skynet_study - 项目地址
相关推荐
SEO_juper1 小时前
跳出率偏高诊断:页面加载慢、内容不对买家需求调整思路
服务器·chrome·seo·跨境电商·外贸·geo·2026
我科绝伦(Huanhuan Zhou)1 小时前
文件备份系统已开源
运维·服务器
hahjee2 小时前
【鸿蒙PC】kcp 移植:AtomCode Skills 4 步速通单文件 C 库适配
c语言·华为·harmonyos
BomanGe12 小时前
NSK紧凑型FA系列精密滚珠丝杠技术解析
运维·服务器·网络·经验分享·规格说明书
zh路西法2 小时前
基于yaml-cpp的C++参数服务器设计2:多级参数配置
linux·服务器·c++
去码头整点薯条983 小时前
网络实验报告9
运维·服务器·网络
AI科技星3 小时前
《数术工坊:非欧射影录》类型:硬核光影·几何本源
c语言·开发语言·网络·量子计算·agi
QiLinkOS3 小时前
极客与商业思维的融合实践(1)
c语言·数据库·c++·人工智能·算法·开源协议
坚果派·白晓明3 小时前
鸿蒙PC】libuv适配:AtomCode Skills一站式指南
c语言·c++·华为·ai编程·harmonyos·atomcode