One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

第 1 部分:NovaCommerce 高可用性的真实含义

一个虚构名称下的真实数字:一个前端服务器、两个固定后端、一个数据库 node 和两个关闭的 standby。第 1 部分定义高可用性并展示瓶颈如何一直移动。

Author

Anichur Rahaman

1 周前13 min read3 views
第 1 部分:NovaCommerce 高可用性的真实含义

在 2026 年 9 月 25 日,学生在线考试期间开始看到 HTTP 429 错误,"请求过多"。造成问题的不是服务器不足。问题出在一条规则上:速率限制器按 IP 地址计数,而一所学校可能有数百个学生共享一个地址。对限制器而言,整个校舍看起来像一个非常没有耐心的人。

十一天后,2026 年 10 月 6 日凌晨 02:28,后端负载测试返回了 2,854 个应用错误。每一条都说同样的话:连接 Valkey 超时。这是另一层的问题,另一种原因,也需要另一种修复。之后是纯粹的 CPU 饱和,这是真正需要更多服务器才能解决的瓶颈。

这个序列就是这个项目的真实故事:瓶颈一直在移动。加服务器不是架构。它是几种解决方案中的一种,也是最后一种。

在 2026 年 9 月下旬到 10 月 7 日之间,我们将一个运行在 DigitalOcean 上的 Laravel 和 Next.js 平台从一个前端服务器和两个固定后端升级到准备好应对定时考试高峰的架构。本部分涵盖起点:业务问题、我们继承的系统、为什么它不够用、对我们来说 "高可用性" 意味着什么,以及最初的测试告诉我们什么。

在我们开始之前:NovaCommerce 是这个案例研究中的虚构名称。我省略了任何能够识别该公司、其服务器或其学生的信息。

这是五部分案例研究 "从单服务器到考试日就绪" 的第 1 部分。NovaCommerce 是虚构名称;架构、数字和错误都是真实的。

流量按计划到达的平台

NovaCommerce 是一个在线平台,销售课程并为学生运行定时在线考试。用户注册、付款、参加实时考试、查看结果并使用 AI 学习辅助。

大多数网络流量是一个山坡:上午上升,晚上下降。考试流量是一个悬崖。想象一个音乐厅的门。几个小时什么都不发生,然后门打开,所有人同时到达。当定时考试开始时,数千名学生在同一分钟内涌入平台。

好消息是这个悬崖有时间表。我们知道它什么时候来。我在关于 流量峰值自动扩展的指南 中用通用术语描述了这个优势。本系列是另一半:一个真实的平台,带有真实的数字。

业务给了我们四个要求,用简洁的语言表达。

  1. 考试峰值是定时的且陡峭的。数千名学生在同一分钟内到达。
  2. 提交内容永远不能丢失。慢页面令人厌烦。丢失的考试提交是学生的工作消失了。
  3. 部署必须是无形的。网站在我们发布版本时必须保持正常运行。
  4. 成本必须保持理性。没人需要的容量也算是一个缺陷。

本系列中的每个技术选择都可以追溯到这四行中的一行。

起点:我们继承的系统

以下是我们在 2026 年 10 月 5 日之前发现的设置,在一个 DigitalOcean 地域和一个私有网络中。

原始设置的图表:学生到达一个前端 droplet,然后是一个后端 load balancer,按 ID 针对两个固定后端 droplet,然后是一个 MySQL node、Valkey 和一个 worker;两个已关闭的 standby droplet 分开存放,仍在计费
两个单点故障(前端和数据库),两个固定后端的列表,以及两个关闭但仍在计费的备用服务器。

前端:一个服务器,八个 CPU,一个收银员

前端是一个单一 droplet,有 8 个 vCPU 和 16 GB 内存,运行一个 Next.js 容器。域名直接指向它,TLS 来自 droplet 上的 certbot。没有 load balancer。如果那个服务器宕了,网站就下线了。

它也浪费了金钱。一个 Node.js 进程大约在一个核心上渲染,所以大部分八个 vCPU 都闲置了:一个有八个收银台但只有一个收银员的商店。

后端:两个服务器和一个无法增长的 load balancer

一个 DigitalOcean load balancer 管理两个固定的后端 droplet,每个有 4 vCPU 和 8 GB。应用是 Laravel(PHP-FPM)在 Docker 中运行在 nginx 后面,每个 node 有两个容器:web 用于 nginx,app 用于 php-fpm。队列(Horizon)、调度器和 Reverb(WebSocket 服务器用于实时更新)运行在一个 worker 上。

load balancer 按其自己的固定 ID 针对每个后端,而不是按标签。这是有名单的宾客名单和一条说 "任何戴着员工徽章的人" 的规则之间的区别。有了名单,新服务器永远无法自行加入。必须有人编辑列表。

这里有好消息,它比什么都重要。后端 web node 已经是无状态的了。会话、缓存和队列存在托管的 Valkey 中,日志去往标准错误。一个不拥有任何东西的服务器就是一个可以删除的服务器。没有这个属性,本系列的其余部分就不可能发生。

数据库和关闭的备用服务器

数据库是一个托管 MySQL "Advanced" node,有 8 vCPU 和 32 GB:单个 node,没有 standby。

然后有两个 "standby" droplet,保作备份。它们被关闭了。一个关闭的 droplet 仍然花钱,启动它需要几分钟。这就像在城外一个被锁起来的车库里放的备用轮胎:它存在,你为它付款,但当你卡在路上时它也帮不上忙。这不是高可用性。

两个较小的问题坐在角落里。load balancer 的 TLS 证书是手动上传的,有过期日期,所以续期是一个有人必须记住的重复任务。而且账户的 droplet 限制是 25,这在两个池可以增长并替换 node 时会成为问题(第 2 部分)。

为什么这不够用

设置的部分如何构建的压力或故障下会发生什么
前端一个 8 vCPU / 16 GB droplet,一个 Next.js 容器,没有 load balancer如果它宕了网站就下线;大多数核心闲置,因为一个 Node 进程大约使用一个核心
后端两个固定的 4 vCPU / 8 GB droplet,由 load balancer 管理,按 ID 针对没有自动增长;一个新服务器永远无法自行加入
数据库一个托管 MySQL node,8 vCPU / 32 GB,没有 standby如果 node 故障就没有第二个 node 接管
Standby两个 droplet,关闭状态关闭时计费;启动需要几分钟

看看那个表中缺少什么:一个 bug。没有什么坏掉了。设置按其设计的方式工作。问题是定时悬崖和一个无法失去服务器、无法自己添加一个、并保持备用容量关闭的设置之间的不匹配。你无法修补不匹配。你必须改变系统的形状,那就是架构的意思。

"高可用性" 在这里真正意味着什么

"高可用性" 是那些每个人都点头但没人定义的短语之一。在我们改变任何东西之前,我们写下了它对这个平台意味着什么。它归结为四行。

没有单个服务器的丢失会导致网站下线。数据库在 node 丢失后仍能幸存。部署和缩减对学生是无形的。容量在考试开始前就准备好,不是五分钟后。

我喜欢一个可以用问题测试的定义。我们能拔掉任何一个服务器的电源并继续服务吗?数据库 node 故障后考试能继续吗?我们能在不被学生察觉的情况下发布版本吗?第一个学生点击 "开始" 时容量是否已经就位?

两条业务规则在其上面:提交从不丢失,成本保持理性。这个图表将每个要求映射到必须交付它的层以及本系列中覆盖该层的部分。

五个要求映射到交付每一个的层和本系列中覆盖它的部分:load balancer 和池(第 2 部分),托管 MySQL 和 standby(第 3 部分),健康检查和受保护的 rollout(第 2 和 4 部分),pre-scaling(第 2 和 5 部分),无状态 node(第 4 部分),加上成本规则
每个要求都有所有者:一个必须交付它的层,和本系列中显示如何交付的部分。

定义的最后一行,"不是五分钟后",是最让人疼痛的。在我们的测试中,自动扩展在 CPU 达到 99% 后大约七分钟添加了第一个额外 node。七分钟在考试在一分钟内开始时是很长的。容量必须在悬崖之前就准备好,而不是在悬崖之后。

最初的测试:瓶颈一直在移动

随着定义的写下,我们做了方法要求的:先测量。在 9 月下旬到 10 月 6 日之间,我们观察生产并运行负载测试,发现了连续的三个问题。它们看起来无关,那就是问题所在。

三个瓶颈的时间线:9 月 25 日在代码中修复的每 IP 速率限制器,10 月 6 日通过调整数据层尺寸修复的 Valkey 连接超时,以及通过容量解决的后端 CPU
连续三个瓶颈,每一个都有不同类型的修复。只有第三个通过添加服务器来解决。

1. 限制器:一个代码问题(9 月 25 日)

在生产中测量:学生在考试期间获得 HTTP 429。全局 API 速率限制器按 IP 计键,因为针对基于令牌的学生的默认 auth guard 是空的,所以限制器无法判断学生是谁。一所学校或移动运营商将数百个学生放在一个 NAT 地址后面,整个建筑共享一个桶。

状态:Implemented。我们将 API 限制器按学生或讲师账户计键,只有客人按 IP。更多服务器无法帮助;规则是在说不,不是硬件。这个 bug 的一个近亲在 10 月 7 日在路由级别的限流中回来,那个故事在第 4 部分。

2. Valkey 连接:一个数据层尺寸问题(10 月 6 日)

在 2026 年 10 月 6 日 02:28,后端负载测试返回了 2,854 个应用 500 错误。每一个都是 RedisException: Operation timed out 在连接时,connect timeout 是 5 秒。Valkey(4 GB,primary 加 standby)在负载下无法足够快速地接受连接。

应用对它的使用方式加重了压力。每个请求都向 Valkey 打开了一个新的 TLS 连接(大约每个请求 5.4 ms CPU)和另一个到 MySQL(大约 3.3 ms)。想象一个商店,每个顾客都被查询和逐次检查通过门。在一群人的面前,门成了队列。

状态:Implemented,然后 Tested。我们在原地调整了 Valkey 到 8 GB,两个 node(primary 和 standby),数据保留,主机不变。然后我们用从 25 到 500 请求/秒的逐步上升重新测试,从两个后端 node 开始,而池在负载下扩展:0 应用错误和 0 后端 5xx。在 load balancer 处有 143 错误,但仅在 node CPU 饱和时。那是预期的 "超出容量" 信号,不是 bug。数据层得到第 3 部分,Valkey 在第 4 部分回归。对于通用理论,见 负载下的数据层。

3. 后端 CPU:一个容量问题

修复了前两个后,剩下的就是纯粹的算术。一个 4 vCPU / 8 GB 后端 node 服务大约 65 到 70 requests/s 的真实 API 混合,在全 CPU 时,大约每个请求 59 ms CPU。之后的每个容量决定都是建立在那个数字之上的。

这是三个瓶颈中唯一一个更多服务器真正解决的,即使在这里时间也是陷阱:第一个额外 node 在 CPU 触及 99% 后约七分钟到达。我们如何让容量提前到达是第 2 部分的主题。

所以顺序是一个限制器(代码),然后 Valkey 连接(数据层尺寸),然后后端 CPU(容量)。如果我们从添加服务器开始,限制器仍然会说 429,Valkey 仍然会超时。

旧设置的成本是多少

在谈论新形状之前,知道旧形状的成本会有帮助。这些是旧 web 层的 DigitalOcean 列表价格(每月)。

项目成本它买了什么
旧 web 层:前端、两个后端、worker、一个 load balancer、两个 standby约 $416/月一个有多个单点故障的运行网站
两个关闭的 standby droplet$112/月没有可用性:关闭时计费,启动需要几分钟
一个额外的后端 node,用于比较约 $0.08/小时真正服务请求的容量

那个 $112,web 层的四分之一多一点,是让我记住的数字。它购买了两个无法服务单个请求的服务器。pre-scaling 一个额外的后端 node 成本约 $0.08/小时,所以为一个晚上提高容量成本数美分。全月为关闭的容量付费是感觉安全的更贵方式。

一个诚实的注意:$416 只覆盖 web 层,不是数据库、Valkey 或存储,所以它不能与完整账单比较。在第 5 部分我会比较苹果和苹果。

方法和本系列的计划

我们在整个项目中重复的模式就是你刚才看到的:测量、找到瓶颈、理解原因、改变架构、再测试、再测量。添加服务器不是架构。架构是选择哪一层改变,以及为什么。

这里是五部分如何组合的。

部分层你会看到什么
1(这部分)起点业务问题、旧设置、高可用性的定义、最初的瓶颈和旧成本
2应用层两个 load balancer、两个 autoscale 池、pre-scaling、基于镜像的部署和 rollout 课程
3数据库从一个 MySQL node 到一个有 standby 的 26 分钟窗口、读/写分离、带 pgvector 的 PostgreSQL
4Valkey、worker、日志队列、worker、日志、监控以及在真实考试期间犯的缩减错误
5证明2026 年 10 月 7 日的真实考试晚上、负载和 soak 测试,以及最终架构

我们也考虑了我们没有构建的想法,并标记了每一个。一个用于两个层的 load balancer 被考虑,然后拒绝。一个 "准备好在几秒内接管的温 standby" 被考虑,但 autoscale 池没有温池,所以我们使用已经服务的备用容量加 pre-scaling。Kubernetes 被考虑以后使用,没有实现。

不是所有东西都完成了,我会说。单个 worker 服务器是一个已知的单点故障;修复它被计划,没完成(第 4 部分)。一个正式的多小时 soak 测试也被计划(第 5 部分)。

我们想要达成什么

目标作为清单,带有构建或测试每一行的部分。

  • 前端和后端层在 load balancer 后面,每个至少两个 node(第 2 部分)。
  • 自己加入和离开的新服务器,没有手工编辑的 node(第 2 部分)。
  • 在考试前提高容量,自动扩展作为安全网(第 2 和 5 部分)。
  • 一个有 standby 的数据库,成本比一个大 node 每月更少(第 3 部分)。
  • 没有本地状态的 web node,所以提交在任何一个 node 死亡时存活(第 4 部分)。
  • 学生看不到的部署和缩减(第 2 和 4 部分)。
  • 从真实考试数据构建的容量模型(第 5 部分)。
  • 一个我们可以逐行解释的账单(第 5 部分)。

我们学到了什么

  • 在购买任何东西之前用失败和事件的术语写下 "高可用性"。一个可以用问题测试的定义比一个口号更好。
  • 一个关闭的 standby 是一个成本,不是可用性。我们的是 $112/月,没有真正的保护。
  • 瓶颈会移动。修复第一个会显示下一个,所以在每次改变后再测量。
  • 不同的瓶颈需要不同的修复:代码(限制器)、数据层尺寸(Valkey)和容量(后端 CPU)。只有最后一个通过更多服务器解决。
  • 无状态 node 是入场券。没有它们,之后的步骤就不会是安全的。

接下来,在 第 2 部分,我们构建应用层:两个 load balancer、两个 autoscale 池、pre-scaling,以及从短 503 闪烁学到的 rollout 方法。

About the Author

Anichur Rahaman

Continue Reading