限时数据(租期、临时增益、缓存条目)的清理有两条路:一是给每个条目挂到点定时器,到期瞬间精确触发——但引擎的个人定时器是有限资源池(F:\底层文件 的定时器 ID 表确认每人可挂数量有上限),五千个限时增益就是五千个定时器,池子瞬间耗尽。惰性方案反过来:不挂任何定时器,读取条目时比对时间戳,过期即视为不存在并顺路回收;再借一条已有的周期扫描任务顺路带走"永不再读"的漏网条目。到期判定的成本是一次时间戳比较,几乎为零。
惰性读取与顺路扫描:buff 表读时校验,过期顺路清除。示例代码如下:
local buffs = {}
local function lazyGetBuff(actorName, buffId)
actor = getplayerbyname(actorName)
local b = buffs[actorName] and buffs[actorName][buffId]
if b == nil then
return nil
end
if os.time() >= b.expire then
buffs[actorName][buffId] = nil
sendmsg(actor, 1, buffId .. " 效果已到期。")
return nil
end
return b
end
local function sweepBuffs()
local now = os.time()
local cleared = 0
for name, set in pairs(buffs) do
for id, b in pairs(set) do
if now >= b.expire then
set[id] = nil
cleared = cleared + 1
end
end
if next(set) == nil then
buffs[name] = nil
end
end
return cleared
end
顺路接入既有扫描示例代码如下:
setontimerex(78, 60000)
local function ontimerex78()
local n = sweepBuffs()
if n > 0 then
sendmsg(nil, 1, "顺路清理过期增益 " .. n .. " 条")
end
end
buffs 双层表按玩家名加 buffId 存条目,expire 时间戳是唯一的到期依据。lazyGetBuff 读取时判定,过期即删即返 nil,业务层无需感知到期细节。sweepBuffs 是顺路回收:借全局定时器 78 每分钟跑一轮,把"永不再读"的条目带走,外层空表也顺手摘除防壳泄漏。整套方案零专用定时器,到期精度从"定时器毫秒级"放宽到"读取时判定加分钟级兜底"。
惰性清理踩过三个坑:一是业务要求"到期瞬间"触发动作(到期发补偿邮件),惰性方案最早在下一次读取时才发现过期,补偿晚了 3 小时被投诉——这类强时效条目仍要走定时器,惰性只覆盖"过期即无效"型的被动数据;二是扫描遍历中直接删除 pairs 正在遍历的键,行为未定义偶尔跳条目,先收集待删清单、遍历结束后统一删;三是 sweep 的全局定时器在停服维护期间停摆,恢复后首扫积压数万条目单次跑 400 毫秒,扫描要限批(每轮最多 2000 条,剩余下轮继续)。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
设计初衷 帮会活动只有攻城与聚餐两板斧:非攻城日帮会频道冷清,成员缺乏共同目标。帮会试炼场设计:帮会自有试炼场(限时挑战波次…
设计初衷 帮会资金靠少数大佬捐献:金主一走帮会资金断崖,普通成员没有参与感也不会珍惜帮会资源。帮会会费设计:成员按职位每周缴…
设计初衷 帮会扩张靠熟人拉人:增长有天花板、新人质量参差、老人不愿带新。募兵编制设计:帮会发布募兵任务包(新人完成入帮任务即…
底层原理 协程体内出错时 resume 返回 ok=false 与错误对象,但协程体若死循环则 resume 永久挂起——错…
底层原理 coroutine.resume 的实参会在协程内成为首个 yield 的返回值;coroutine.yield …
业务场景 挂摊卖药每小时断货:玩家下线前上满货,两小时后摊位空转。摊位自动补货封装:上摊时设定补货仓库(背包或帮会仓),定时…