完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
排行榜是刺激竞争的核心功能:等级榜、战力榜、活动积分榜。玩家分数变化的频率远高于榜单刷新的频率,实时更新的关键在"分数变化时的有序插入"与"并列排名的正确展示"。本文给出一套纯 Lua 的实时排行榜实现。
榜单按分数降序维护,分数变化时用二分查找定位新位置,从旧位置移动到新位置:
function Rank.update(list, playerId, newScore)
local oldIdx = indexById(list, playerId)
local entry = { id = playerId, score = newScore }
if oldIdx then table.remove(list, oldIdx) end
local lo, hi = 1, #list
while lo <= hi do
local mid = (lo + hi) // 2
if list[mid].score >= newScore then lo = mid + 1
else hi = mid - 1 end
end
table.insert(list, lo, entry)
return lo -- 新名次
end
二分定位让单次更新的比较次数从 O(n) 降到 O(log n),1000 条榜单的更新耗时约 0.01ms。并列分数按"先到先得"处理(比较函数在分数相等时比较进入时间戳)。
展示名次与列表下标是两回事:并列分数应显示同名次(1、2、2、4 的竞赛排名规则)。实现上在渲染时计算:与前一 entry 分数相同则继承其名次,否则名次等于当前下标。缓存每个 id 的上次名次,名次变化时给客户端推一条变化消息,前端做名次升降的箭头动画。
榜单保留前 N 名(如 500 名),分数跌出榜单的玩家从列表移除但保留其个人成绩查询(单点查询走另一个接口)。定时全量快照(每小时一次)用于对账与回溯,实时更新与定时快照的差异要在可控范围内。榜单接口的读写分离:读走快照 + 增量消息,写走 update 接口,避免读写互相阻塞。这套结构在 996 的各类活动榜上可原样复用。
榜单数据落盘选在每天低峰(凌晨四点)做一次全量快照,实时更新的内存榜单与快照每日核对一次分数总和,差异超过阈值即触发全量重建,防止长周期运行中的累计误差。