FastComments.com Blog

Wed Sep 10 2025
...

FastComments 现已全球分布

新内容

之前,FastComments 的架构非常传统。我们有应用服务器、数据库和一些其他服务。这些在两个区域(美西和欧盟)中复制。如果你在法国,想查看我们全球数据中心中一个客户的评论线程,你的请求必须一路发送到美西来获取评论数据。

现在不再是这样!现在评论数据和所有媒体资产在我们的全球部署中为客户进行全球复制,对于在我们欧盟部署中的客户,我们在欧盟有三个存在点进行数据复制。你的请求将被发送到离你最近的节点。

以往的工作方式

除了在各个区域和云服务提供商之间有多个活跃副本的数据库外,所有服务都是以每种服务类型一个实例的方式部署。这意味着每个区域都有一个应用服务器、一个 PubSub 服务器和一个媒体服务器。计划是在能够的情况下进行垂直扩展,因为这样保持了简单。编写代码很简单——你始终知道在访问数据库时可以“读取自己的写入”。基础设施也很简单,除了安全更新会导致一分钟的停机时间。

问题

问题显而易见地出现在我们达到容量时。所以我们开始优化,最终不得不提升该服务的实例大小。

在 Linode 上,这开始变得成本高昂,因为基于我们的性能测试,144美元的实例大约相当于20美元的 OVH 节点,即使我们更换托管提供商,我们的各处都会有单点故障——而像 OVH 这样的提供商在维护故障时往往比 Linode 更长的解决时间。

RiR :)

在最初的几年中,FastComments 的 PubSub 和媒体服务是用 Java 编写的。Java 被选择的原因是相对较高的性能和投入的努力,经过多年的调整 GC ,尝试 G1GC、Shenandoah 和 Z1 后,我们决定不再使用 Java。内存使用开销实在太大,因为这些服务在完成后非常静态,Java 的好处逐渐消失。此外,这些服务往往不得不处理“雷鸣般的潮流”问题,这意味着 JVM 在 JIT 尚未启动时试图处理峰值流量。这些服务非常适合切换到 C++ 或 Rust。

我们选择 Rust 是因为我们不是 C++ 专家,网络代码中的错误可能会暴露一个客户的数据给另一个客户。Rust 可以帮助我们防止此类问题。

我们本来就想整合这些服务,所以虽然我们可以使用 GraalVM 再次进行优化,但我们决定转向 Rust,彻底解决这个问题。

迁移并非没有困难。这些服务必须终止 SSL,支持 HTTP 1.1、HTTP/2 等等。它们执行许多任务,例如同时管理多个数据流,从边缘的硬盘 LRU 缓存、S3、数据库读取媒体,并在一个网格中进行通信。Java 生态系统中的 Vertx 和 Netty 非常适合这些工作。我们在推动库生态系统到极限,由于对 Rust 库没有太多经验,我们经历了一些试错。这导致了一些停机,我们对此表示歉意。

我们还尝试了不同的内存管理器,最终选择了 mimalloc 用于我们的自定义 DNS 服务器,并使用 libc 作为传输层。我们不运行 Nginx 或 Apache,而是使用自己的组合,即一个自定义 DNS 服务器,根据每周从 Maxmind 重建的内存索引在全球路由客户端,以及我们用 Rust 实现的传输层,它与其他传输实例保持网格连接。传输层终止 SSL,处理 PubSub 工作,并充当我们的 CDN。这样做的好处是减少在服务之间迁移时的开销,更少的基础设施开销/抽象。缺点是可见性,因此良好的指标非常重要。

就最终性能而言,Rust 服务的内存使用量约为 Java 服务的 10-30%,尽管我们做了所有的工作。我为了兴趣阅读《Java 并发实战》等书籍,所以虽然不是专家,但我对编写快速的 JVM 服务略知一二,而用 Rust 完成这目标要容易得多。此外,面对大量订阅者的消息激增时,JVM 服务需要花费 40% 的时间在 GC 上,而我们的代码更像是游戏引擎,而不是典型的服务器。虽然我不能说自己是 Rust 的忠实粉丝,但对于那些执行大量工作且在初始开发后不怎么变化的服务,它是完美的。我们的主要业务逻辑仍然使用 TypeScript。

新架构

新架构不再有“宠物”节点。相反,每个服务器都是一个完整的克隆,所有服务几乎具有相同的配置。它们各自运行传输、DNS、应用服务器和数据库副本。所有节点都保持完整磁盘加密,使用 Dropbear 自动解锁。

每台服务器运行路由传输,终止请求并处理它们作为 websocket、http 流或 CDN 请求。这些服务器相互连接,任何给定的服务器提供全球网络到其本地 DNS 服务器的映射,实时告诉 DNS 每个活跃节点在全球的位置。

新架构的一个好处是冗余。如果一个区域的客户强烈请求,其他区域仍然上线。

应用程序代码现在必须非常清楚哪些查询可以命中最近的节点,哪些必须连接到可能距离较远的数据库主节点。犯错可能会显著降低性能。这也意味着某些区域的写入可能会很慢,这需要仔细的调整和优化。我们在代码内部遵循了一种模式,其中所有访问数据库的方法都带有 readPreference 参数,以便调用者一直到最顶层都必须明确决定如何查询。

其好处是非常好的横向扩展用于读取。FastComments 以读取为主,但我们不能忘记我们的版主!版主在全球日复一日地工作,我们希望他们的体验保持良好。在此次推出的一部分中,我们一直在与一些版主合作,以确保审核工具保持快速。

我们还可以独立选择硬件,这很有趣且有回报。新服务器是 i5-13500 和 Ryzen 5 5600X 的混合,欧盟的一些使用较旧的 Xeon。在我们的基准测试中,所有这些都比我们在其他提供商那里探索的更昂贵的服务器要快得多。缺点是需要更多的设置工作,但我们已经对此进行了自动化,同时对 SMART 磁盘监控故障等进行了自动化。

做这些事情意味着我们可以继续提供具有竞争力的定价。

部署

在过去几个月中,随着我们重写服务并迁移到新的托管提供商,部署的过程一直很曲折,感谢您们的耐心。

在初始部署中,我们的指标显示请求超过 100ms 的数量增加。我们尽量不让太多请求花费这么长时间。

渐进式进展
慢请求

我们仍在逐步改善某些区域的性能。感谢所有至今提供反馈的人。

使用 API 时的考虑

API 仍然保持强一致性——你可以读取自己的写入——以保持向后兼容,并使开发人员的工作简单。为了让开发者选择性能而非一致性,我们计划暴露 readPreference 参数。好处是我们还可以为这些 API 调用提供积分折扣。

所有公共端点,例如用于提供公共评论小部件的端点,都始终从该节点的最近(本地)数据库读取。

为什么不只是使用常规 CDN

评论线程并不是静态的,它们是动态的,并且将实时流应用于过时的静态数据也不起作用,因为当你以匿名用户查看一个线程时,你会得到一个“匿名会话”。在这个匿名会话中,你可以阻止和标记其他用户,你必须显示匿名用户是否喜欢某个评论,等等。评论线程也可能受到 SSO、身份验证或用户组的限制。

最后,“渐进增强”的类应用程序,其中动态数据映射到来自 CDN 的静态数据,会给你糟糕的体验,因为内容会跳动或者在几秒后发生变化。我们更希望避免这种情况。

现在谁拥有我的数据

你的数据不再存储在 Linode 上。它在 Hetzner 和 OVH 之间实时复制,并且所有后端服务器之间的通信都通过 TLS 完成。我们维持了几个遗留 Linode 实例用于出站 Webhook 代理,但不再存储任何数据或媒体在 Linode 上。

总结

像所有主要版本一样,我们很高兴能花时间优化、测试并正确发布这一变化。经过这项工作,FastComments 的可扩展性和正常运行时间在长远来看应该会更好。如果你发现任何问题,请在下方告诉我们。