Sun Mar 29 2026
...
FastComments 已经为太空做好准备!
! 本文包含技术术语
新动态
每个 FastComments point-of-presence 现在会在本地进行写入,并异步地将其复制到所有其他节点。这将提供比以前系统更强的耐久性,并使某些地区的用户的审核工具更快,但也有一些权衡。
“准备好进入太空”有点乐观,但我们的想法是,我们可以将 FastComments 部署到不同的行星,并最终使系统保持同步。然而,冥王星的用户将不得不等待大约一天才能在即将到来的发票页面上看到变更,因为目前每个地区只能有一个主节点来聚合计费信息。
一些历史,为什么要改变
当 FastComments 最初推出时,我们有一个非常典型的架构。我们有一个代理层、一个应用层、一个数据库以及一些副本,后来又在各个地区和云提供商之间增加了副本以提高冗余。
最终,我们将数据库副本移动到大多数用户所在的所有区域,并在那里部署了应用,同时创建了自己的 DNS 和代理系统(将在后来的博客文章中描述),以将请求路由到最近的应用节点。这使得读取速度变快,但写入速度却变慢,因为现在不再等待一次 HTTP 往返请求到后端,而是等待一次 HTTP 往返请求到附近的节点,而该节点可能会多次向主节点进行数据库写入。这不好!
因此,为了应对这个问题,我们重组了应用程序的许多区域,以便在函数参数中接受 readPreference,这样调用者可以决定他们的读取数据过时程度。另外,我们还将更多的写操作(比如在审核操作中写入审核统计数据)设为“触发后忘记”。这并不理想,但确实显著加快了响应速度。
我们在全球范围内运行 Mongo 时遇到的一个问题是网络分割。如果足够多的节点被隔离,读取将停止,因为每个节点都无法确定是否可以提供读取服务。有一些解决方法,但边缘案例让人觉得复杂。这不是一个理论问题—它发生过多次,导致我们夜里三点接到警报,我们对这一点感到厌烦,甚至尝试对 Mongo 进行调优,以便在副本集选举不确定时能够处理长达一分钟的时延。遗憾的是,举个例子,圣保罗到法尔肯施泰因的网络在我们的一些托管提供商中实在不佳。调优拥塞控制等方法有所帮助,但并未解决问题。
理想的解决方案是,假设您可以接受某些权衡,能够在那节点本地接收写入(该节点具有不错的硬件、RAID 等,不太可能崩溃),并告诉用户他们的数据已保存。您还可以在该位置有一个第二节点作为热备份以提高耐久性。
所以这就是我们最终达成的方案。俄勒冈、弗吉尼亚、法尔肯施泰因、圣保罗、新加坡,各自都是自己的副本集,并接受写入。欧盟部署(尽管只有三个 PoP)具有相同的行为。
它是如何工作的
前面的部分涵盖了一些内容,但简而言之,它是 CRDT-lite。我们创建了一个代理(用 Rust 编写,当然)在应用程序与 Mongo 之间工作,使其成为多主模式。这个代理是对等感知的,管理检查点、复制、监视和初始同步。它是 Mongo 复制系统的多主替代方案,包括某些 DDL 命令。
与其他工具的不同之处在于它不尾随 oplog。尾随 oplog 或使用变更流是行不通的,因为它们只向您显示写入后的对象最终状态,无法处理冲突。您需要捕捉每个 $set、$inc 操作并复制该操作本身。
这是一个特定领域的解决方案。并不适用于所有产品。可以说它是领域驱动设计 :)。它对我们有效,因为从一开始我们非常仔细地只对文档中更改的字段使用 $set—例如,我们从不使用 Mongo 的 replaceOne。计数器也是一样。您从不会做 SET VOTES = 5。相反,您会写 INCREMENT VOTES BY 5,因为这允许最终一致性。分布式锁通过完全避免它们来处理。每个集群只有一个节点设置了运行 cron 的标志。虽然这看起来有限,但我们可以购买数 TB 的 RAM 服务器,因此我们可以承受这种风险和复杂性。
读取自己的写入
对于使用 API 的开发者,您应该能够像以前一样读取自己的写入(进行 API 调用以创建评论,然后列出评论并在列表中看到新条目)。注意事项是您无法跨地区进行此操作。如果您的后端仅在一个地区运行,例如 us-west,那么您应该能够在大多数情况下读取自己的写入,除非发生以下事件:在您的写入和读取之间,该节点宕机并且您的 DNS 缓存更新为指向下一个最近的节点。只要不发生这种情况,读取自己的写入是可靠的。
测试与迁移
系统中约一半的代码是测试工具、框架和测试。尽管如此,发布过程还是有点坎坷,停机时间比预期长(欧盟 1 小时,全球 20 分钟),但我们很高兴能够通过这个里程碑,并感谢您的耐心!
结论及其对您的意义
FastComments 现在应该比以往更快、更耐用,现在我们可以回去继续开发新功能 :)
干杯!
