skynet框架:批量服务管理方案
skynet很经典的用法是一个节点会有批量的服务跑相同的模块逻辑。服务的生命周期管理显然是跟业务强关联的,需要根据实际业务对应做适配的生命周期管理方案。显然最直接的方案就是服务常驻,跟进程的生命周期同步,当服务的数量级不大时,认为消耗可控,方案是适用的,也避免过度设计。
这里想谈的是单节点数千服务的场景下对应的服务管理,以游戏项目中常用基础模块为例;
管理器需要处理的核心逻辑是,服务的创建销毁和服务消息的转发;
根据不同的服务类型定义出业务内唯一的id,比如玩家代理服务的id是玩家角色id,聊天频道代理服务是频道序号等;
local skynet = require("skynet")
local agent_manager = {
_id_addr = {} -- {业务id:服务地址}
}
-- 创建/查询服务
function agent_manager.get_or_load_agent(agent_name, id)
local addr = agent_manager._id_addr[id]
if addr then
return addr
end
addr = skynet.newservice(agent_name, id)
agent_manager._id_addr[id] = addr
return addr
end
-- 销毁服务
function agent_manager.destroy_agent(id)
local addr = agent_manager._id_addr[id]
if not addr then
return
end
agent_manager._id_addr[id] = nil
if addr then
agent_manager.agent_call(id, "exit")
end
end
-- 转发消息:call
function agent_manager.agent_call(id, cmd, ...)
local addr = agent_manager.get_or_load_agent(agent_name, id)
if not addr then
return
end
return skynet.call(addr, "lua", cmd, ...)
end
-- 转发消息:send
function agent_manager.agent_send(id, cmd, ...)
local addr = agent_manager.get_or_load_agent(agent_name, id)
if not addr then
return
end
skynet.send(addr, "lua", cmd, ...)
end
-- 广播消息
function agent_manager.broadcast_agent(ids, cmd, ...)
local online_ids = online_filter(ids)
for _, id in ipairs(online_ids) do
local addr = agent_manager.get_or_load_agent(agent_name, id)
if addr then
skynet.send(addr, "lua", cmd, ...)
end
end
end
return agent_manager
玩家代理服务(client):显然服务的生命周期需要跟玩家上线下线同步,即玩家上线时创建分配服务,下线时销毁服务,当然实际方案通常会多加一些处理,比如优化重登情况:下线时冻结服务指定一段时间,超时才销毁退出,优化玩家频繁上线下线造成的性能消耗;同时玩家代理服务还持有网络套接字连接,需要处理数据存盘和句柄释放等操作;
代理服务的销毁流程是:(玩家下线—>关闭socket—>数据存盘—>通知管理器冻结服务—>重登唤醒激活/超时未重登释放)
-- user agent
function user_agent.logout(fd, dbdata)
socket.close(fd)
db.save(dbdata)
skynet.call(agent_manager_addr, "agent_freeze", id)
end
-- agent manager
local EXPIRE_SEC = 60 * 3 -1
function agent_manager.agent_freeze(id)
local function timeout_closure()
return agent_manager.destroy_agent(id)
end
skynet.sleep(EXPIRE_SEC, timeout_closure)
end
聊天频道代理服务(chat):聊天频道的特点是玩家聚集度明显,分布不均。这里以 为每个频道创建独立服务 的方案来讨论。那么不同的服务访问热度是不同的,请求会集中在少数的频道服务中。这时候对服务使用闲置销毁的管理方案,一定时间内没有请求到达则自动销毁退出服务,每次请求都检查服务是否存在,否则先创建服务;
-- agent manager
local agent_manager = {
_id_addr = {},
_id_t = {},
}
local AGENT_EXPIRE_SEC = 60 * 10
function agent_manager.check_agent_expire(id)
local addr = agent_manager._id_addr[id]
if not addr then
return
end
local last_call_t = agent_manager._id_t[id]
if last_call_t and last_call_t + AGENT_EXPIRE_SEC <= skynet.now() then
agent_manager.destroy_agent(id)
end
end
-- 转发消息:call
function agent_manager.agent_call(id, cmd, ...)
local addr = agent_manager.get_or_load_agent(agent_name, id)
if not addr then
return
end
agent_manager._id_t[id] = skynet.now()
return skynet.call(addr, "lua", cmd, ...)
end
-- send/broadcast 同理
btw,这个方案可能需要关注的地方:1. 服务创建动作由请求触发,当请求存在并发场景时,需要关注处理临界区情况;2. 定时销毁的间隔(上述的AGENT_EXPIRE_SEC)需要根据实际业务来评估合适的值,不合理的销毁间隔反而会适得其反增大性能压力;
周期赛事服务(activity):赛事活动的特点是,服务有明显的有效期,在一段固定的时间内生效,那么适用于集中创建销毁的管理方案,在赛事开始时批量创建拉起服务,在赛事结束时批量销毁;当服务量级过大时,可以考虑引入上述闲置销毁处理方案;
function agent_manager.multi_load_agent(ids)
for _, id in ipairs(ids) do
local addr = agent_manager._id_addr[id]
if not addr then
addr = skynet.newservice(agent_name, id)
agent_manager._id_addr[id] = addr
end
end
end
function agent_manager.multi_destroy_agent(ids)
for _, id in ipairs(ids) do
local addr = agent_manager._id_addr[id]
if addr then
agent_manager.destroy_agent(id)
end
end
end
团队代理服务(team):每个团队使用独立服务代理,服务应该是常驻的,但量级是O(N),N是团队总数量,上限不可控,且团队节点通常是单点节点,系统存在理论上限。团队服务的特点是活跃度差异大,这里适用于 lru 管理方案,评估系统安全承载的服务数量上限,按活跃度规则进行有序管理,末位淘汰销毁超出上限的冷服务。
local AGENT_CAPACITY = 30000
local lru = Lru.new(AGENT_CAPACITY)
function agent_manager.get_or_load_agent(id)
local addr = agent_manager._id_addr[id]
if addr then
return addr
end
addr = skynet.newservice(agent_name, id)
agent_manager._id_addr[id] = addr
lru.push(id, addr) -- 加入lru管理
return addr
end
function agent_manager.check_lru_capacity()
if not lru.full() then
return
end
local id = lru.pop()
agent_manager.destroy_agent(id)
end
-- 更新访问活跃度
function agent_manager.agent_call(id, cmd, ...)
local addr = agent_manager.get_or_load_agent(agent_name, id)
if not addr then
return
end
lru:update(id, skynet.now())
agent_manager.check_lru_capacity()
return skynet.call(addr, "lua", cmd, ...)
end
-- send/broadcast 同理
本文来自博客园,作者:linxx-,转载请注明原文链接:https://www.cnblogs.com/linxx-/p/18439370
浙公网安备 33010602011771号