前言
在游戏中,配置是必不可少的一项,那skynet是如何加载配置并实现配置共享的,接下来带着问题去了解skynet是如何实现配置共享的。
Question
- 为了实现数据共享,skynet都提供了哪些接口?
- 如何利用这些接口,实现配置共享服务?
- 其他服务如何访问配置?
- 如何更新配置?并如何通知其他服务的?
Answer
为了实现数据共享,skynet都提供了哪些接口?
答:可以从 sharedata.lua 文件中看到,lua层提供如下接口,如何使用文件内都有说明及案例。
- 查询共享数据:sharedata.query(name)
- 创建共享数据:sharedata.new(name, table)
- 更新共享数据:sharedata.update(name, table)
- 删除共享数据:sharedata.delete(name)
- 深拷贝数据:sharedata.deepcopy(name)
如何利用这些接口,实现配置共享服务?
答:
- 在 config 中配置
配置目录及配置加载脚本。如:server-config.lua
lua
...
-- 配置文件目录
config_path = root .. "cfgs/" --配置目录
config_loader_path = root .. "src/public/cfgloader.lua" --配置加载脚本
- 首先需要创建配置目录
cfg/,将需要的配置全部放入此目录,例如文件夹中的loginCfgs.lua。 - 编写对应的配置加载脚本
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
- 编写配置共享服务代码
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服务举例
- 首先在 ppmain 服务中创建 setting服务,注册 setting服务。代码如下:ppmain.lua,并且创建一个 ping服务,在ping 服务中访问配置。
lua
local skynet = require "skynet"
skynet.start(function ()
skynet.newservice("setting")
local ping = skynet.newservice("ping");
end)
- 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)
- 由代码可知,需要
require setting,setting.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
- 在
preload.lua中添加如下代码,当在其他服务查询到setting服务时,就会调用setting.Init(),那么其他服务就可以通过local cfg = setting["loginCfgs"]获取配置信息。
lua
-- ...
if skynet.localname(".setting") then
local setting = require "setting"
setting.Init()
end
如何更新配置?并如何通知其他服务的?
答:
- 通过
sharedata.update("setting", str)更新配置,str为cfgLoader.lua文件的内容 - 每个引用配置的服务在
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) 创建共享对象。
value 为 cfgloader.lua中的返回值 allCfgs。
代码 local cobj = sharedata.host.new(tbl) 会调用到 corelib.lua 中,其实就是调用 lua-sharedata.c 中的 lnewconf(...)方法,此方法进行如下步骤:
- 创建独立 Lua 状态机
- 转换 Lua 表为 C 结构
- 返回 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) 方法时,会有如下流程:
- 检查缓存,如果命中则直接返回
- 从
sharedatad服务查询,获取 C 对象指针 - 再次检查缓存(可能在等待期间其他协程已查询)
- 创建共享对象盒子(
ctrl),调用lua-sharedata.c中的lboxconf(...)方法sd.box是 C 函数,创建ctrl结构(userdata)ctrl.root指向 C 对象ctrl的元表提供__index、__pairs等方法
- 启动监控协程
- 缓存并返回盒子
更新共享配置
当调用 sharedata.lua 中的 sharedata.update(name, v, ...) 方法时,会有如下流程:
- 调用 sharedatad 的 update 命令
- sharedatad 创建新的 C 对象
- 标记旧对象为 dirty
- 通知所有监控协程
- 监控协程收到通知,更新盒子的 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