从本地单体到可扩展的 AWS 架构:票务销售案例研究
Source: Dev.to

问题陈述
想象以下情景:一个票务销售应用运行在物理服务器(本地)上。
目前,该应用是一个使用 Node.js 编写的单体应用;它在同一服务器上使用 MySQL 数据库进行持久化,并将静态文件(如活动海报)直接存放在本地硬盘上。
当著名 artist 的演出门票开售时,这种架构会出现关键问题:
- 服务器因流量激增而崩溃,
- 数据库被锁定,
- 图片加载极其缓慢。

为了解决这些根本问题,决定将应用迁移到 AWS。这就是基于以下功能需求进行架构规划的起点:
| 需求 | 描述 |
|---|---|
| 高可用性 (HA) | 如果服务器或可用区出现故障,应用必须继续运行且不中断。 |
| 可伸缩性 | 系统必须在需求时处理用户负载并吸收重大活动期间的流量峰值。 |
| 持久性 | 事务完整性至关重要;任何一次销售都不能丢失。 |
| 安全 | 数据库必须受到保护并与公共互联网隔离。 |
挑战
我们需要通过四个基本支柱来构建解决方案:
- 计算 – 应用运行在哪里,如何管理流量?
- 数据库 – 使用哪项服务来提供 MySQL,如何在不使系统饱和的情况下优化读取?
- 静态存储 – 如何提供海报图片以确保快速加载?
- 网络与安全 – 如何组织 VPC 网络,在保护数据的同时允许用户访问网页?
架构提案
计算
在 EC2 实例 上运行应用程序,由 Auto Scaling Group 管理。
在前端放置 Application Load Balancer (ALB),在不同的 可用区 (AZs) 中的实例之间分发请求,确保高可用性。
数据库
使用托管服务 Amazon RDS for MySQL。
为优化性能,我们将评估两种策略:
- 只读副本 – 用于扩展读密集型工作负载。
- Amazon ElastiCache – 缓存频繁查询,降低主数据库负载。
(我们将在测试后决定最佳方案。)
静态内容
将海报图片迁移到 Amazon S3 存储桶,并通过 Amazon CloudFront(CDN)提供,以缓存内容并显著降低全球加载时间。
网络与安全
在 VPC 中实现三层架构:
| 层级 | 放置位置 | 目的 |
|---|---|---|
| 负载均衡器 | 公共子网 | 互联网流量的入口点 |
| 应用服务器 | 私有子网 | 运行 Node.js 应用 |
| 数据库 | 私有子网 | 托管 RDS 实例 |
使用 安全组 严格限制 各层之间的流量(例如,仅负载均衡器可以访问应用服务器,且仅应用服务器可以访问数据库)。


深入探讨:分布式系统挑战
上述架构满足了基础设施需求,但从单体应用迁移到分布式环境会暴露出两个关键的逻辑问题。
1. 用户会话
原始应用将会话存储在服务器的 RAM 中。 在新架构中,Auto Scaling + Load Balancer 的组合意味着请求可能被路由到与创建会话的实例不同的实例上,从而导致用户意外退出登录。

我们该如何解决?
将应用改造为无状态。不要在本地存储会话,而是将其外部化到 Amazon ElastiCache(Redis 或 Memcached)。作为内存数据存储,它提供亚毫秒级延迟,并确保即使用户的请求落在不同的实例上,会话仍然集中可用。
2. 数据一致性(竞争条件)
这里我们重新审视使用 Read Replicas 还是 ElastiCache 的争论。
用户 A 购买了一张票。
几毫秒后,用户 B 检查同一个座位。如果使用只读副本,会有一个小的延迟(复制滞后),在此期间 用户 A 的购买尚未在所有副本中同步。这可能导致 用户 B 试图购买已经售出的座位,产生错误,甚至出现超卖。

我们如何在不让数据库饱和的情况下实现即时可用性?
理想的解决方案是 ElastiCache(Redis)。只读副本由于上述延迟并不适合实时库存控制。相反,Redis 让我们利用其 原子性。因为 Redis 按顺序处理操作,它可以作为完美的控制机制:如果多个购买请求同时针对同一座位到达,Redis 会将它们排队并逐个处理,仅允许第一个事务成功。这解决了竞争条件,并将读取流量从主数据库中卸载。

结论
将本地 environment 迁移到云端不仅仅是搬迁服务器(Lift & Shift);更是对我们的应用如何处理状态和并发进行重新思考。
通过在架构中集成 Amazon ElastiCache (Redis),我们不仅提升了读取速度——还解决了分布式系统中最复杂的两个问题:
- 无状态应用中的会话管理。
- 竞争条件下的数据完整性。
有了这套架构,我们已经从一个在著名艺术家需求下崩溃的服务器,转变为一个弹性、稳健的基础设施,能够根据需求自动扩展。

