skynet 共享数据项目级应用

前言

在游戏中,配置是必不可少的一项,那skynet是如何加载配置并实现配置共享的,接下来带着问题去了解skynet是如何实现配置共享的。

Question

  1. 为了实现数据共享,skynet都提供了哪些接口?
  2. 如何利用这些接口,实现配置共享服务?
  3. 其他服务如何访问配置?
  4. 如何更新配置?并如何通知其他服务的?

Answer

为了实现数据共享,skynet都提供了哪些接口?

答:可以从 sharedata.lua 文件中看到,lua层提供如下接口,如何使用文件内都有说明及案例。

  1. 查询共享数据:sharedata.query(name)
  2. 创建共享数据:sharedata.new(name, table)
  3. 更新共享数据:sharedata.update(name, table)
  4. 删除共享数据:sharedata.delete(name)
  5. 深拷贝数据:sharedata.deepcopy(name)

如何利用这些接口,实现配置共享服务?

答:

  1. 在 config 中配置 配置目录配置加载脚本。如:server-config.lua
lua 复制代码
...
-- 配置文件目录
config_path = root .. "cfgs/" --配置目录
config_loader_path = root .. "src/public/cfgloader.lua" --配置加载脚本
  1. 首先需要创建配置目录 cfg/,将需要的配置全部放入此目录,例如文件夹中的 loginCfgs.lua
  2. 编写对应的配置加载脚本 cfgloader.lua。代码如下,将目录下所有的配置添加到 allCfgs 中
lua 复制代码
local skynet = require "skynet"
local string_match = string.match

local allCfgs = {}

local cfgPath = skynet.getenv("config_path")
local allFiles = ListFiles(cfgPath, true)
for k, v in pairs(allFiles) do
    local cfg = dofile(v)
    local cfgName = string_match(v, "([^/]+)%.lua$")
    allCfgs[cfgName] = cfg --以文件名为key,return 的 table作为 value值
end

return allCfgs
  1. 编写配置共享服务代码 setting/main.lua,代码如下:
lua 复制代码
local skynet = require "skynet"
local sharedata = require "skynet.sharedata"
require "skynet.manager"

skynet.start(function()
    local cfgLoaderPath = skynet.getenv("config_loader_path")
    local fd = io.open(cfgLoaderPath, "r")
    local str = fd:read("*a")
    sharedata.new("cfgs", str) -- str可以为代码块

    skynet.register(".setting")
end)

其他服务如何访问配置?

答:拿 examples/ppmain.lua服务举例

  1. 首先在 ppmain 服务中创建 setting服务,注册 setting服务。代码如下:ppmain.lua,并且创建一个 ping服务,在ping 服务中访问配置。
lua 复制代码
local skynet = require "skynet"

skynet.start(function ()
    skynet.newservice("setting")
    local ping = skynet.newservice("ping");
end)
  1. ping 服务访问配置代码如下:ping.lua,
lua 复制代码
local skynet = require "skynet"
local setting = require "setting"

skynet.start(function ()
    local cfg = setting["loginCfgs"]
    print("cfg", cfg, cfg.name)
    skynet.error("cfg:%s,name:%s", cfg, cfg.name)
end)
  1. 由代码可知,需要 require settingsetting.lua 实现如下,调用 Init 可以共享对象盒子(box)挂载此服务。
lua 复制代码
local skynet = require "skynet"
local sharedata = require "skynet.sharedata"

local M = {}

local bInit = false

function M.Init()
    print("setting init")
    if not bInit then
        bInit = true
        skynet.init(function()
            local box = sharedata.query("setting") --当前服务中获取共享数据引用
            setmetatable(M, {__index = box})
        end)
    end
end

function M.GetConfigByName(name)
    return M[name]
end


return M
  1. preload.lua 中添加如下代码,当在其他服务查询到 setting 服务时,就会调用 setting.Init(),那么其他服务就可以通过 local cfg = setting["loginCfgs"]获取配置信息。
lua 复制代码
-- ...
if skynet.localname(".setting") then
    local setting = require "setting"
    setting.Init()
end

如何更新配置?并如何通知其他服务的?

答:

  1. 通过 sharedata.update("setting", str) 更新配置, strcfgLoader.lua 文件的内容
  2. 每个引用配置的服务在sharedata.lua中monitor(...)用来监听,配置的update

实现原理分析

sharedata 模块

创建 sharedatad 服务

lua 复制代码
-- sharedata.lua
skynet.init(function()
	service = skynet.uniqueservice "sharedatad"
end)

由上述代码可知,require "sharedata" 会在服务 skynet.start(...) 调用后,创建唯一服务 "sharedatad"

创建配置

setting/main.lua中的代码可知,创建setting服务时,会调用 sharedata.new("cfgs", str)此时的 str 为代码块。此方法会向 sharedatad服务发生lua类型消息。

调用到 sharedatad.new(...) 方法。

创建共享数据

sharedatad.lua中的 function CMD.new(name, t, ...) ... end 实现可知,此方法会执行代码块,并返回执行结果ret,然后调用 newobj("cfgs", value) 创建共享对象。

valuecfgloader.lua中的返回值 allCfgs

代码 local cobj = sharedata.host.new(tbl) 会调用到 corelib.lua 中,其实就是调用 lua-sharedata.c 中的 lnewconf(...)方法,此方法进行如下步骤:

  1. 创建独立 Lua 状态机
  2. 转换 Lua 表为 C 结构
  3. 返回 C 对象指针(lightuserdata)(lightuserdata可在服务间共享)
    创建完成后,会记录到sharedatad服务的共享服务池pool中。
    参数类型:
  • table: 直接使用
  • string: 加载文件或执行代码
    • 以 "@" 开头:加载文件
    • 否则:执行代码字符串
  • nil: 创建空表

示例:

lua 复制代码
-- 直接使用表
sharedata.new("config", {version = "1.0"})

-- 加载文件
sharedata.new("config", "@/path/to/config.lua")

-- 执行代码
sharedata.new("config", "return {version = '1.0'}")

查询共享数据

当调用 sharedata.lua 中的 sharedata.query(name) 方法时,会有如下流程:

  1. 检查缓存,如果命中则直接返回
  2. sharedatad 服务查询,获取 C 对象指针
  3. 再次检查缓存(可能在等待期间其他协程已查询)
  4. 创建共享对象盒子(ctrl),调用 lua-sharedata.c 中的 lboxconf(...) 方法
    1. sd.box 是 C 函数,创建 ctrl 结构(userdata
    2. ctrl.root 指向 C 对象
    3. ctrl 的元表提供 __index、__pairs 等方法
  5. 启动监控协程
  6. 缓存并返回盒子

更新共享配置

当调用 sharedata.lua 中的 sharedata.update(name, v, ...) 方法时,会有如下流程:

  1. 调用 sharedatad 的 update 命令
  2. sharedatad 创建新的 C 对象
  3. 标记旧对象为 dirty
  4. 通知所有监控协程
  5. 监控协程收到通知,更新盒子的 root 指针

特点:

  • 客户端无需重新查询
  • 盒子的 root 指针自动更新
  • 旧对象延迟回收(引用计数为 0 时)

业务服务使用

因为在 preload.lua 中添加了如下代码,当查询到cfgs服务时,就会调用 setting.Init(),然后通过 local box = sharedata.query("cfgs") 查询到共享对象盒子,

然后设置元表,就可以通过setting["xxxx"]形式直接访问。

lua 复制代码
if skynet.localname(".setting") then
    local setting = require "setting"
    setting.Init()
end
相关推荐
荒--1 小时前
MSF 使用
linux·运维·服务器
狮子再回头2 小时前
relhat9.1 sshd配置
linux·服务器·网络
不爱编程的小陈2 小时前
深入解析 Go 网络 I/O 的底层引擎:从 epoll 到 netpoll
服务器·网络·golang
Aurorar0rua2 小时前
CS50 x 2024 Notes Arrays - 04
c语言·开发语言·学习方法
烁3472 小时前
liunx命令不完整版
linux·运维·服务器
vsropy2 小时前
cmake版本不对不能直接删/无法source
linux·运维·服务器
wuminyu3 小时前
Java世界中StringTable源码剖析
java·linux·c语言·jvm·c++
Navigator_Z3 小时前
LeetCode //C - 1095. Find in Mountain Array
c语言·算法·leetcode
火山上的企鹅3 小时前
Codex实战:APP远程升级服务搭建(四)Node 服务端自动识别 APK 信息
android·服务器·git·github·qgc
IT WorryFree3 小时前
ESXi 全维度监控方式完整分类(按使用场景排序)
运维·服务器·网络