已解决:Martinit‑Kit:Typescript 运行时,在用户之间同步状态的多人应用/游戏

发布: (2026年3月4日 GMT+8 20:10)
10 分钟阅读
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原有的格式、Markdown 语法以及技术术语。

执行摘要

TL;DR: 多人应用经常因网络延迟和竞争条件而遭遇实时状态同步问题,导致客户端不同步。本文介绍了 Martinit‑Kit,一个 TypeScript 运行时,主要通过 权威服务器模型 实现稳健的状态同步,为所有连接的用户建立唯一的真相来源。

问题

  • 网络延迟 是多人应用中状态不同步的根本原因。
  • 当多个用户同时尝试修改相同状态时,延迟会导致 竞争条件

权威服务器模型

  • 行业标准,用于稳健的状态同步。
  • 服务器充当 唯一真相来源,验证意图,并向所有客户端广播官方状态更改。

乐观 UI

  • 通过即时更新客户端屏幕来提升感知性能。
  • 风险: 如果服务器拒绝更改,会出现突兀的回滚。最适合冲突较少的操作。

事件溯源

  • 提供所有状态更改操作的 不可变日志
  • 提供完整的审计追踪和历史回放能力。
  • 对于复杂系统而言,这是一项重大的架构转变。

为什么会痛苦

“我永远不会忘记 Project Chimera 的演示。我们正在向副总裁们展示我们新的协作设计工具。一切都很顺利,直到两位高管同时尝试移动同一个组件。在一台屏幕上它向左卡住;在另一台屏幕上它向右移动。随后,整整十秒钟,它在两个位置之间闪烁,随后彻底消失在数字虚空中。那间屋子的寂静……震耳欲聋。”

共享状态不是一个特性;它是一场 分布式系统的终极Boss战

简单演练

  1. User A 点击按钮 → 客户端向 api‑gw‑01 发送“更改状态”消息。
  2. 该消息的传输耗时约 ≈ 80 ms。在这 80 毫秒内,世界仍在转动。
  3. User B 尚未收到 User A 的更新,点击了另一个会修改同一状态的按钮。他们的消息现在正处于传输途中。
  4. 服务器收到了 两个冲突的指令
    • 谁会获胜?
    • 最后一个会覆盖第一个吗?
    • 如果第一个更重要怎么办?

没有唯一、无可争议的真相来源和明确的冲突解决规则,客户端必然会逐渐脱节,导致混乱、困惑,以及在 VP 演示期间组件消失。

常见解决方案(从“伪装成功”到全规模架构)

1. 乐观 UI – “伪装成功”

对感知性能非常有帮助。思路是立即更新用户自己的界面,假设服务器会同意。

// 超简化伪代码
function handleMoveButtonClick(itemId: string, newPosition: Position) {
  // 1️⃣ 立即更新我们自己的 UI。感觉很快!
  const previousPosition = updateLocalItemPosition(itemId, newPosition);

  // 2️⃣ 告诉服务器我们做了什么。
  api.sendItemMove(itemId, newPosition)
    .catch(error => {
      // 3️⃣ 哎呀——服务器拒绝了!回滚我们的乐观更改。
      console.error("Move rejected by server:", error);
      updateLocalItemPosition(itemId, previousPosition); // 跳回去!
      showErrorToast("Couldn't move the item.");
    });
}

专业提示: 这会让用户感受到“魔法般”的快速响应,但如果服务器拒绝更改(例如权限问题或冲突),UI 元素会“跳回”原来的位置。仅在冲突概率低的操作中使用。

2. 权威服务器 – “成年”方案

在此模型中,客户端是 愚笨的:它永远不决定最终状态,只发送 意图 给服务器。服务器是 唯一真相来源

流程:

  1. 用户 点击 “向左移动项目”。

  2. 客户端 发送一条消息,例如:

    {
      "action": "MOVE_INTENT",
      "itemId": "abc-123",
      "direction": "left"
    }

    UI 可能会显示一个加载指示,但它 不会 立即移动项目。

  3. 服务器(例如 game‑state‑worker‑03)接收意图,进行验证,检查冲突,并在内存或像 Redis 这样的高速缓存中更新规范状态。

  4. 服务器 将新的、官方的状态广播给 所有 已连接的客户端(包括发起者)。

  5. 所有客户端 接收新状态并渲染。每个人都保持完美同步,因为他们都是服务器的镜像。

Martinit‑Kit 这样的框架专门为简化此模式而构建。它们处理 WebSocket、状态广播和调和的样板代码,让你专注于服务器端逻辑——这才是关键所在。

3. 事件溯源 – 当“当前状态”不足以应对时

有时状态逻辑如此复杂,仅存储 当前状态 并不够。你需要知道 它是如何得到的

引入事件溯源。 与其存储最终结果,不如在不可变日志中记录每一次操作(事件)。

示例 – 银行账户:

事件数据
ACCOUNT_CREATEDinitialBalance: $0
DEPOSIT_MADEamount: $100
WITHDRAWAL_MADEamount: $50

当前余额($50)通过 回放 这些事件计算得出。这种做法提供了完美的审计能力和重建任意过去状态的可能性,但代价是增加了架构复杂度。

选择合适的方法

情境推荐策略
快速原型 / 演示乐观 UI(带有明确的回滚处理)
生产级多人游戏应用权威服务器 + 类似 Martinit‑Kit 的库
复杂领域逻辑,需要审计追踪事件溯源(通常与权威服务器结合使用)

最后思考

  • Latency ≠ Instantaneous – 互联网并非瞬时;请为延迟做好设计。
  • Single Source of Truth – 防止漂移和竞争条件。
  • Clear Conflict Rules – 提前决定如何解决冲突意图。

通过理解分布式系统的底层物理原理并选择合适的架构,你可以把“分布式系统终极对决”变成一个可管理、可预测的过程。同步愉快!

Warning: 不要轻视这条路径。这是一次根本性的架构转变。它需要不同的思维方式和工具(如 Kafka 或专用事件存储)。对合适的问题它极其强大,但它并不是简单聊天应用的快速解决方案。

那么,哪种方案最适合你?

方案实现速度用户体验稳健性 / 可扩展性
Optimistic UI非常响应(但可能出现“跳动”)低(易出现竞争条件)
Authoritative Server中等(框架有帮助)良好(操作有轻微延迟)高(行业标准)
Event Sourcing慢(工作量大)良好(与权威模型相同)极高(复杂但强大)

我的建议?
先采用 Authoritative Server 模型。它在可靠性和实现工作量之间达到了最佳平衡。寻找能让你更快实现的工具。如果你的应用感觉迟缓,可以在非关键操作上加入一些 Optimistic UI。如果你的应用发展成下一个 Google Docs,那就是个好问题——可能是时候研究 Event Sourcing 了。

现在去构建一些酷炫的东西,尽量不要让 VP(版本发布)把它弄坏。

👉 阅读原文请访问 TechResolve.blog

支持我的工作
如果本文对你有帮助,你可以请我喝咖啡:
👉

0 浏览
Back to Blog

相关文章

阅读更多 »