从 0 到 100 万用户的系统演进之路

本文是对《搞定系统设计:面试敲开大厂的门》第一章的学习和思考,同时也是对那道经典面试题"从输入URL到看到页面经历了哪些步骤"的回答。

设计一个支持数百万用户的系统具有挑战性,是一个需要持续完善和不断改进的过程。在这一章中,我们构建一个支持单个用户的系统,并逐渐扩展以服务数百万用户。

一、单服务系统

最基础的服务只需要一个Web服务器、域名和公网ip。浏览器根据域名,去DNS服务找到对应的公网ip,访问Web服务器,获取HTML页面。

二、数据库服务器

随着用户的增长,单个Web服务器已经不够了,我们需要一个数据库服务器。

使用什么数据库? 你可以在传统关系型数据库和非关系型数据库之间进行选择。

关系型数据库也称为关系数据库管理系统(RDBMS)或SQL数据库。其中最流行的包括MySQL、Oracle数据库、PostgreSQL等。关系型数据库使用表格和行来表示和存储数据。你可以使用SQL在不同的数据库表之间执行连接操作。非关系型数据库也称为NoSQL数据库,分为四类:键值存储(用于存储缓存)、列存储(半结构存储,用于存储用户日志、用户画像)和文档存储(半结构存储,用于存博客、电商订单等,相比列存储最大的优势在于可以基于内容查询,而列存储只能基于key来查询)、图形存储(用于适合存储朋友关系)。

三、负载均衡器

数据库服务器有了,我们的系统涌入了更多用户,把Web服务器挤爆了,这个时候就轮到负载均衡上场了。

负载均衡可以把网络流量根据策略打到不同的Web服务器上,从而避免其单点故障问题。负载均衡器本身可能成为单点故障,因此实际系统通常部署多台负载均衡器并配合主备或双主切换,实现高可用。

四、数据库复制

一个数据库服务器还不够的情况下,还可以继续扩展------垂直扩展和水平扩展。垂直扩展通过增强单机性能提升系统能力,简单但受硬件上限和单点故障限制;相比之下,水平扩展更适合大规模与高可用场景。

在实际应用中,读请求通常远多于写请求,因此系统往往配置多个从库以分担读负载。数据库复制通过将主库数据同步到从库,实现水平扩展并提升系统可用性。

现在我们已经基本搞懂网络层和数据层了,接下来让我们提升一下响应速度。

五、缓存层

缓存层是一个临时的数据存储层,比数据库更快。拥有独立的缓存层的好处包括更好的系统性能、减轻数据库负载的能力以及能够独立扩展缓存层。

使用缓存的几个注意事项:

  1. 何时使用缓存?

缓存的核心作用是提升读性能、减轻后端负载、降低延迟。适合使用缓存的典型场景:

  • 读多写少:如商品详情、配置项、用户资料等;
  • 生成成本高:复杂计算、聚合查询、外部接口结果;
  • 能容忍短暂不一致:如统计类、排行榜、推荐列表。

不建议使用缓存的情况:

  • 数据频繁变动或需要强一致性(如交易余额、库存扣减);

  • 缓存命中率低或数据体量过大导致内存浪费。

  1. 使用缓存常见问题

缓存虽然能提速,但引入了一致性与可靠性风险

(1)一致性问题

现象: 缓存与数据库更新不同步,出现"脏读"或"旧数据"。 原因: 数据写入数据库后,缓存未及时更新或删除。 典型场景: 用户更新资料后,页面仍显示旧信息。 常见策略:

  • 异步通知机制:通过消息队列或订阅系统触发缓存更新。
  • 版本控制或时间戳校验:校验缓存数据是否过期。

(2)出错与过载风险

缓存系统一旦异常或过期,可能引发访问风暴,冲击数据库。

缓存穿透 查询不存在的 key,每次都打到数据库。 使用 布隆过滤器 或缓存空值。
缓存击穿 热点 key 过期瞬间被并发访问。 给热点 key 加锁或使用 逻辑过期机制。
缓存雪崩 大量 key 同时过期,导致数据库压力激增。 设置 随机过期时间、分批刷新、限流与预热。
系统异常 缓存宕机或访问超时。 启用 降级机制(返回旧数据或默认值)。
  1. 清除缓存

(1)时间(过期策略) 缓存应具备生命周期(TTL),防止长期脏数据。

  • 固定 TTL:统一设置过期时间,简单易控;
  • 滑动 TTL:访问时刷新过期时间,保证热点数据常驻;
  • 主动刷新:后台任务或订阅机制定期更新缓存;
  • 随机过期时间:避免大量 key 同时过期造成雪崩。

(2)驱逐(Eviction)策略 当缓存空间不足时,系统会根据策略淘汰数据:

  • LRU(Least Recently Used) :淘汰最久未使用的;
  • LFU(Least Frequently Used) :淘汰访问最少的;
  • FIFO(First In First Out) :按写入顺序淘汰;
  • 自定义优先级:关键数据可标记为不过期或延迟淘汰。

驱逐策略应根据业务访问模式选择:

  • 热点集中 → LRU 更合适;

  • 数据访问分布均匀 → LFU 效果更优。

缓存层这一招,确实让我们的系统"飞"了一大截------热门数据不用次次查数据库,响应速度嗖嗖的。可问题也随之冒出来:离我们机房近的用户体验丝滑,远在海外或偏远地区的用户,却依旧卡得想摔电脑。明明服务器已经优化到牙缝里去了,为什么他们访问还是慢?其实瓶颈不在服务器性能,而在------"距离"。这时候,我们就得请出加速全球访问的神器:CDN(内容分发网络)

六、CDN

说到 CDN,最先想到的就是静态内容缓存。它的作用很简单------把图片、视频、脚本、样式表等不常变化的资源缓存在离用户更近的节点上,让用户能更快加载网页、减轻源站压力、提升访问体验。做静态缓存是需要注意设置好故障回退策略、自动和手动过期策略当然还有开销。

在现代 Web 架构中,动态内容缓存 已成为提升性能与可扩展性的重要手段。它不同于静态内容缓存仅针对图片、脚本、样式表等固定资源,而是面向那些会根据请求参数、用户特征或业务逻辑而变化的内容,例如个性化页面、搜索结果、推荐信息等。

动态内容缓存的核心思想,是让 CDN 或边缘节点在一定程度上缓存由服务端实时生成的 HTML 页面或 API 响应,从而减少源站计算压力、缩短用户响应时间。以 Amazon CloudFront 为例,它支持通过灵活的 缓存键(Cache Key) 配置,将不同请求的特征纳入缓存维度,实现更智能的缓存策略。

CloudFront 的缓存键默认包含"域名 + URL 路径",但开发者可以根据业务需要进一步指定:

  • 查询字符串(Query String) :根据请求参数缓存不同页面版本,如 ?lang=en?lang=zh-CN
  • Cookie:针对不同用户状态或偏好缓存对应页面,例如登录与未登录状态。
  • 请求头(Header) :根据设备类型或语言首选项(如 User-AgentAccept-Language)缓存不同响应。
  • 请求路径(Path Pattern) :为不同业务路径设置差异化缓存策略。

这种机制使 CDN 能够在边缘层实现对部分动态页面的缓存与复用,从而显著提升访问速度并减轻源站负载。

七、无状态网络

是时候横向扩展网络层了。在前文的设计中,用户登录信息是存储在Web服务器(网络层)中的,这叫做有状态网络,由于要网络层服务器要"记住"用户状态,所以难以扩展。无状态网络层是指服务器在处理请求时,不保存客户端的任何上下文状态。每个请求都是独立、完整、自足的,服务端只依赖请求中携带的信息就能处理完逻辑并返回响应。简单就是: "服务器忘记上一次你是谁,只看这一次你说了什么。"

将用户状态从网络层移到专门的持久化存储(一般用NoSQL,比较快),网络层就可以实现自动扩展(基于网络流量自动地增加或者减少Web服务器)了。

八、数据中心

网站继续发展,而且吸引了非常多的国际用户,要提高可用性以及在更广的地理区域提供更好的用户体验,让网站支持多数据中心就非常关键。

要设置多数据中心,必须先解决如下技术难题:

•流量重定向。要有能把流量引导到正确数据中心的有效工具。

•数据同步。不同地区的用户可以使用不同的本地数据库或者缓存。在故障转移的场景中,流量可能被转到一个数据不可用的数据中心。常用的一个策略是在多个数据中心复制数据。

•测试和部署:设置多数据中心后,在不同的地点测试你的网站/应用是很重要的。而自动部署工具则对于确保所有数据中心的服务一致性至关重要。

九、消息队列

为了进一步扩展我们的系统,我们需要解耦系统中不同的组件,这样它们就可以单独扩展了。在现实世界中,很多分布式系统用消息队列来解决这个问题。解耦使消息队列成为构建可扩展和可靠应用的首选架构。有了消息队列,当消费者无法处理消息时,生产者依然可以将消息发布到队列中;就算生产者不可用,消费者也可以从队列中读取消息。

考虑以下用例:你的应用支持修改图像,包括裁剪、锐化、模糊化等,这些任务都需要时间来完成。Web服务器把图像处理的任务发布到消息队列。图像处理进程或服务(Worker)从消息队列中领取这个任务,并异步执行。生产者和消费者都可以独立地扩展。队列的规模变大以后,可以加入更多的Worker,以减少处理时间。如果队列在大部分时间中都是空的,就可以减少Worker的数量。

十、日志、收集指标与自动化

对于一个只有几台服务器的小网站,记录日志、收集指标和自动化只是锦上添花的实践而非必需的工作。但是当网站发展成为大企业提供服务的平台时,这些工作就是必需的了。

记录日志:监控错误日志非常重要,因为它可以帮助识别系统的错误和问题。你可以监控每个服务器的错误日志,也可以用工具把各个服务器的日志汇总到一个中心化的服务中,方便搜索和查看。

收集指标:收集不同类型的指标数据,有助于获得商业洞察力和了解系统的健康状态。以下几个指标很有用:

•主机级别指标:CPU、内存、磁盘I/O等。

•聚合级别指标:比如整个数据库层的性能,整个缓存层的性能等。

•关键业务指标:每日活跃用户数、留存率、收益等。

自动化:当系统变得庞大且复杂时,就需要创建或者使用自动化工具来提高生产力。持续集成是一个很好的做法。在这种做法中,每次代码检入(check in)都需要通过自动化工具的审核,使团队能及时发现问题。同时,将构建、测试和部署等流程自动化,可以显著提高开发人员的生产力。

奉上一张完整的系统架构图:

十一、当用户超过100万

当网站或应用的用户数量超过100万时,就需要进行更多的调整和采用新的策略来扩展网站。比如,你可能需要优化系统,并把它解耦成更小的服务。

相关推荐
喵手4 小时前
Java与Microservices架构的结合:构建可扩展、高可用的系统!
java·架构·华为云
爱刘温柔的小猪6 小时前
企业级混合存储架构:MySQL + MinIO 混合存储实践
数据库·mysql·架构·对象存储
快乐非自愿6 小时前
5种主流的API架构风格
架构·api
500佰7 小时前
京东后端架构技术,Pipline 设计 解决复杂查询逻辑
后端·架构
俞凡8 小时前
[大厂实践] 少即是多:Zendesk 长时间作业执行优化
架构
小璐乱撞8 小时前
图解源码:万字解析 Spring AI 到底是怎么实现 MCP 的
java·后端·架构
小璐乱撞8 小时前
图解源码:万字解析 Spring AI 到底是怎么实现 MCP 的(源码部分)
java·后端·架构
失散138 小时前
分布式专题——49 SpringBoot整合ElasticSearch8.x实战
java·spring boot·分布式·elasticsearch·架构