结算
Category
system / 玩法系统。节点定位:结算把「打完一局 / 做完一笔操作」变成不可争议的记录——权威位置、结算凭证、生效时机、落库语义、失败恢复五件事。过程与判定在 server 树、数值口径在 numerical 树、资产存储在 database 树,本页只管结算本身的机制与口径。
Definition
结算是一次状态迁移的提交点:对局数据 → 结算凭证(胜负/得分/奖励清单)→ 权威层写入(资产、榜单、任务进度)→ 可对账的流水。它与支付系统同构:幂等、可追溯、可补偿。区别只在触发源——支付由外部请求触发,结算由对局终局触发,而对局本身长在会话与同步之上。
Problem
结算的坑按出现顺序:
- 权威缺位:客户端算分、服务端盖章。这是 安全与权威 案例里的直接账单——服务端只校验格式的每一天,都是提款窗口。
- 双写:奖励既没发又发了——凭证与落库之间没有幂等键,重试、重连、崩溃恢复都可能把同一笔结算执行两次或零次。
- 时机错位:断线瞬间丢结算、赛季切换时结算跨版本。终局不是一个时刻,而是一段窗口;窗口没有定义,就没有恢复。
- 不可对账:玩家说少发了,运营拿不出凭证重放。发流水与凭证对不上,结算系统就只是「大概率正确」。
先定「谁有资格算」,再定「何时算、怎么落」;顺序反了,安全与一致性都要事后打补丁。
Algorithm
权威位置
结算只能发生在服务端。客户端上报的是触发信号(「我打完了」)与过程数据(操作流水,供服务端复算),不是结果。分数由服务端算——这是 排行榜 「分数必须服务端算」的同一口径在结算上的推广。
结算凭证
一次结算 = 一条不可变凭证:(对局 id, 参与者集合, 结果, 奖励清单, 规则版本)。凭证先行、落库跟随:
text
match_id: m-20261007-000481
participants: [u1001, u1002]
result: { winner: u1001, score: { u1001: 1520, u1002: 1487 } }
rewards: [ {item: gem, n: 30}, {item: ticket, n: 1} ]
rule_version: season-12-v3- 凭证是幂等键的载体:落库方按
(参与者, 对局 id, 奖励项)去重,重复投递直接跳过 - 凭证带规则版本:历史凭证按旧规则可重放,规则热更不打断历史对账(热更边界见 热更新体系)
- 凭证先于落库持久化(哪怕只是本地日志):崩溃后可从凭证重放,丢不了账
生效时机
| 时机 | 延迟 | 幂等实现 | 失败恢复 | 适用 |
|---|---|---|---|---|
| 对局终局(实时) | 秒级 | 凭证即时落库 + 去重键 | 重放凭证 | 竞技/匹配类,奖励即时反馈 |
| 定期批结算 | 分钟~小时 | 批次号 + 逐笔去重 | 批次重跑(天然幂等) | 赛季/每日任务/排行清算 |
| 混合 | — | 实时发参与奖 + 批量清算赛季 | 两层凭证 | 主流组合 |
- 终局钩子要显式:对局「结束」是状态机事件,不是连接断开——断线重连后的对局还在,重连对账之后才算完(重连语义见 服务恢复)
- 批结算要可重跑:批次按
batch_id去重,重跑等于空转;不做去重的批结算在重跑时双发
落库语义
多目标写入(资产 + 榜单 + 任务进度)是一笔事务还是多条 + 补偿,取决于存储侧能否同源:
- 同库:单事务包住全部写入,失败全回滚——最省心,要求资产、榜单、进度同库(见 事务与一致性)
- 跨库:Saga 补偿——凭证先行,各目标逐一落库,失败按凭证逐项补偿;补偿逻辑本身要幂等
- 榜单是结算的常见下游:分数进热层、权威分进宽表的组合见 排行榜 的热冷分层
失败恢复与对账
- 恢复路径:凭证在,账就在——重放凭证到落库层,去重键保证最多执行一次
- 对账:发流水(实际落库记录)与凭证逐笔对,差集就是少发/多发清单;口径治理见 BI 数据口径
- 资产侧的账务模型(货币分层、流水形态)见 经济系统
Used By
- BigWorld(commit
088d3b84,路径相对programming/bigworld/):落库是显式动作——DBApp::writeEntity 处理 writeEntity 消息(server/dbapp/dbapp.cpp:2288-2294),下线/复活时的持久化实体登记在同文件(:743-745附近);周期自动存盘未见于源码——结算时机由脚本层显式触发,脚本不调就不落库。 - Colyseus(commit
e620123):结算钩子位在房间生命周期——onCreate/onJoin/onLeave/onDispose(packages/core/src/Room.ts:674、:682、:704、:789),onDispose 是框架给的对局终点;框架无内置游戏结果持久化(database.boot只拉平台侧账号 SQL 层,packages/core/src/Server.ts:462)——结算层全部自建。
Related
关系链:权威位置 → 结算凭证 → 生效时机 → 落库语义 → 失败恢复 → 对账。逐段回答「为什么需要下一个」:
- 权威定了才有凭证:凭证是权威结果的物证,没有它,幂等与对账都无从谈起。
- 凭证定了才有时机:实时还是批量,取决于凭证的产出节奏与下游容忍的延迟。
- 时机定了要落库:单事务还是补偿链,由资产存储的同源程度决定。
- 落库了要对账:凭证与流水对得上,结算系统才从「大概率正确」变成「可证明正确」。
上游:安全与权威(谁有资格算的安全动机)、技能系统(结算的产出来源之一);下游:事务与一致性、BI 数据口径、排行榜(榜单写入方)。
Reference
- 本页为工程经验归纳;两家引擎事实以文内 commit 锚定的源码引用为准
- 幂等与补偿的事务口径:database 树事务与一致性
- 术语对照:Settlement(结算)、Settlement Voucher(结算凭证)、Idempotency Key(幂等键)、Reconciliation(对账)