单库过亿的行数让索引与备份双双臃肿:按角色 ID 哈希把数据切成 16 个分片,每个分片独立建库独立备份,写入的压力被水平摊开。分片的核心是路由的确定性与跨片查询的代价控制。
---按角色 ID 哈希路由到 16 个分片
local SHARD_COUNT = 16
function Shard.route(userId)
local h = 5381
for i = 1, #userId do
h = (h * 33 + userId:byte(i)) % 2147483647
end
local idx = h % SHARD_COUNT + 1
return Shard.conns[idx]
end
function Shard.save(userId, data)
local conn = Shard.route(userId)
conn:exec("REPLACE INTO roles SET ?", data)
end
路由用 DJB 哈希把角色 ID 映射到 16 个分片之一,同一角色永远落在同一分片,路由函数无状态可随处调用。哈希选型避开了直接取模——纯取模对字符串 ID 的分布不均,DJB 的雪崩效应让 ID 相近的角色散开。单分片控制在 500 万行以内,索引的树高与缓存命中率维持在健康区间。
---跨片聚合:排行榜的 scatter-gather
setontimerex(47, 60)
function Shard.topLevel()
local merged = {}
for i = 1, SHARD_COUNT do
local rows = Shard.conns[i]:query(
"SELECT name, level FROM roles ORDER BY level DESC LIMIT 10")
for _, r in ipairs(rows) do
merged[#merged + 1] = r
end
end
table.sort(merged, function(a, b) return a.level > b.level end)
return merged
end
点查走单分片零浪费,聚合查询必须 scatter-gather:16 个分片各取前 10 再内存归并,沙巴克战报与排行榜这类聚合的延迟取决于最慢的分片。聚合的频率控制:排行榜每分钟一刷并缓存,查询风暴不打穿分片。扩容的预留:分片数从 16 到 32 的迁移用一致性哈希把搬移量压到一半以内,扩容窗口避开在线高峰。
各分片的行数与延迟分位监控,分片间的倾斜超过 30% 提示哈希分布异常;跨片聚合的耗时监控,超过 2 秒的聚合改异步出结果。
分片键曾经用了账号 ID 而角色 ID,一个账号多角色的数据挤在同一分片,倾斜明显,改角色 ID 后分布均匀。跨片事务曾经被直接使用,两阶段提交的复杂度失控,业务改造成单分片内自洽加补偿。聚合查询曾经不带 LIMIT 直捞全表,一个分片 800 万行打爆内存,聚合强制带界。
分片数按三年增长预估一次到位,扩容是计划内工程不是临时起意。路由层对业务透明,SQL 不感知分片,切分只动路由配置。分片的备份错峰滚动:每天备份 4 个分片,四天滚动一轮全量,备份的 IO 峰值被削平。
本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
实战应用:用在哪里 全服 400 多个 NPC 脚本在开服瞬间集中加载,引擎进程启动耗时 47 秒,值班期间重启就是 47 …
实战应用:用在哪里 行会跨图集合靠世界喊话,从比奇赶到沙巴克动辄三分钟,集合效率低。行会驿站给每个行会一个专属传送网络:驿站…
实战应用:用在哪里 跨服战场周日结算夜出了双事故:30 名玩家积分凭空翻倍,随后 12 名玩家积分变成负数。两条线索指向同一…
实战应用:用在哪里 烈火剑法点到一半想换逐日剑法,没有遗忘功能只能养小号,新手加点点废的投诉长期霸榜。技能遗忘与重修框架管三…
实战应用:用在哪里 战斗日志全量滚动,烈火剑法暴击、治疗、拾取一场下来上千条,列表越长滑动越卡。改造分两件事:内存里只留最近…
实战应用:用在哪里 夏季活动缺一个全服参与的轻竞速,选定龙舟:五人一队从白日门河道上游划向盟重码头,河道布设障碍与顺流带,全…