S58

大规模系统设计

点击正文段落选定收藏或划线位置;未选定时使用屏幕内第一段。

课内目录

1 大规模系统设计要回答什么

把文件服务从实验机器搬到多人使用的环境,问题就不再只是“程序能否运行”。有人上传日志,有人反复读取配置,有人用脚本并发下载,后台还要校验文件。服务进程、文件系统和网络接口共享有限资源。某个请求等待磁盘时,其他请求可能继续进入;连接尚未关闭时,文件句柄已经被占用。大规模系统设计要说明这些资源怎样分配、工作怎样分开,以及超过承受范围时怎样保持可解释的行为。

本课使用1个具体场景:团队内部的文件读写服务。客户端通过HTTP上传文件、读取文件和查询后台校验状态;服务运行在Linux主机上,文件内容放在文件系统中,元数据记录对象标识、版本、大小与访问权限。缓存保存部分常用读取结果,后台进程生成摘要。接口可以是GET /files/{id}、PUT /files/{id}和GET /tasks/{id}。这些只是便于讨论的边界,不要求使用某种框架。

设计首先要回答“成功是什么”。读取成功应当返回调用者有权访问的指定版本;上传返回成功,需要说清内容是否已经达到约定的持久化条件,还是仅被接收等待处理。进程把字节写入缓冲区、文件可以被其他请求读到、机器断电后仍能恢复,是不同承诺。入门方案不必展开存储内部实现,却必须选定自己能够验证的成功语义,不能用含糊的“已保存”覆盖差异。

接着确定规模的边界。每天有多少请求、忙时每秒有多少请求、文件通常多大、最大允许多大、客户端会等待多久,都影响容量。同样的请求数,读取小配置与下载大日志消耗的网络带宽完全不同。请求来源也要明确:内部可信脚本仍可能因循环错误产生突发流量,外部请求则还需要身份验证与速率保护。规模是负载条件,不是组件数量。

文件服务的请求、资源与观测边界
文件服务的请求、资源与观测边界

这张图不是部署清单。初始版本可以让多个职责由同1个进程承担,只要边界清楚、指标能区分,就能够逐步演进。设计文档还应写出不处理的范围,例如暂不支持跨地域写入、单文件上限固定、后台任务允许延迟完成。明确限制能让后续测量有意义,也使“超过限制返回什么”成为可以测试的接口行为。

2 容量、QPS 与尾延迟目标

QPS表示每秒处理的请求数量,但至少要区分到达、接收和成功完成。入口每秒收到6000个请求,限流拒绝1000个,并不意味着后端成功处理了6000个。还要写明统计窗口:1分钟平均值可能掩盖数秒内的尖峰。容量讨论应同时保留原始请求量、拒绝量、错误量和成功量,避免通过提前失败让吞吐指标显得漂亮。

延迟目标要对应明确的请求集合。假设小文件读取要求p99不超过200ms,含义是指定窗口内99%的这类请求延迟不超过200ms;它不表示每个请求都快,也不描述剩余1%的最坏情况。应同时观察超时与错误,否则仅对成功请求统计会隐藏大量失败。大文件传输宜分别记录首字节等待与完整传输耗时,不能把它与小配置读取混成1条曲线。

平均耗时只能用于初步估算。若系统稳定、每秒完成6000个请求,平均请求在系统内停留0.05s,那么平均在途请求约为6000×0.05=300。这是观察窗口中的平均关系,不是线程池应直接设置为300的依据。连接复用、等待文件读取、发送给慢客户端都会占用不同资源;延迟上升后,在途数量也可能增加。把并发上限设得很大,常常只是容许更多请求排队。

容量至少沿CPU、内存、磁盘空间、文件系统访问和网络吞吐分别检查。内存预算包括缓存内容、对象索引、进程基础占用和在途缓冲区。文件系统既可能受容量限制,也可能因大量小文件耗尽inode。网络预算要计算真实发送的字节,而不只看请求数。每类资源都需要留出操作系统、后台任务和波动的空间,不能假设所有资源都能长期满负荷运行。

余量来自测量与故障要求。若某后端在规定文件分布、错误率和延迟目标下,实测能安全处理800次回源/秒,规划可以只分配其中80%,即640次/秒。这是本方案选择的预算,不是通用法则。若要求停掉1个实例后继续达标,还要按剩余实例重新计算。正常时看似充足的容量,可能在维护或故障时立即变成排队。

目标最终要形成可检验的句子:在规定文件大小分布、读写比例和权限检查条件下,峰值持续指定时间,读取成功率与p99达到约定值,同时队列不持续增长。还要注明测试是否包含冷缓存、后台任务及慢客户端。脱离这些条件的“支持6000QPS”只是数字,无法指导上线与扩容。

3 拆分、缓存与异步边界

拆分的直接目的,是让不同工作拥有可控的资源与失败范围。读取通常希望迅速返回,上传会持续占用连接,后台摘要则允许稍后完成。把后台任务移到独立进程,可为其设置较低并发,减少与读取争抢CPU和磁盘。但拆分后要处理进程间交接、状态查询与失败恢复;即使只用本机目录作为交接点,也必须定义任务何时可见、处理失败如何记录。

缓存适合重复读取且能够定义有效期的数据。缓存键至少应能区分对象与版本;若缓存内容因用户权限而异,还要正确区分访问范围,或在命中后执行必要的授权检查。把文件标识直接当作所有人的共享键,可能让性能优化变成越权读取。缓存命中也不意味着没有成本:服务仍要处理请求、检查权限并发送响应内容。

读路径中的校验、命中和回源边界
读路径中的校验、命中和回源边界

缓存未命中后访问原始数据的过程称为回源。设计应预先回答:缓存暂时不可用时是否全部回源,回源最多允许多少并发,新写入何时能被读到。简单可行的策略是内容带版本,更新后使用新版本键;若允许读取旧版本,则明确最长陈旧时间。不要把缓存当成唯一数据来源,也不要默认删除缓存与更新文件能跨进程同时完成。

异步适合那些不必阻塞当前响应、且能够表示处理中状态的工作。上传后生成摘要可以异步;读取所需的权限判断不能因为耗时就随意推后。若接口返回“已接收”,客户端应获得任务标识,并能查询等待、运行、成功或失败。排入任务并不等于任务完成,文档与状态码必须与实际承诺一致。

上传发布与后台任务的责任边界
上传发布与后台任务的责任边界

队列只能暂存工作,不能创造处理能力。假设任务每秒到达100个,后台只能完成80个,积压就每秒增加20个。队列需要容量上限、最长等待目标和满载策略,例如暂缓接收新任务。任务重试还可能重复发生,因此可用任务标识检查同项工作是否已经完成。入门阶段先把状态、重复处理和失败记录讲清楚,再讨论更复杂的分布式机制。

拆分、缓存和异步都应对应已观察到的问题。若当前瓶颈是下载出口带宽,增加缓存未必降低发送字节;若当前是权限查询过慢,增加摘要工作进程也无法缩短读延迟。先画出每个请求经过的边界,再把优化放到实际消耗最大的环节,才能解释收益从哪里来。

4 动手:估算一次读写路径容量

现在为文件服务建立可手算的模型。假设忙时读请求6000次/秒,写请求200次/秒,平均读响应体32KiB,平均上传体128KiB。读缓存命中率为90%,写入暂不经过读缓存。这里的平均大小只服务于吞吐估算;压测仍要保留真实大小分布,特别是允许的最大文件,不能让平均值掩盖大对象。

读回源速率为6000×(1−0.9)=600次/秒。即使命中缓存,成功响应内容仍要发给客户端,因此内容出站约为6000×32÷1024=187.5MiB/秒。上传内容入站约为200×128÷1024=25MiB/秒。这些只是内容字节,尚未计入HTTP头、加密、重传、内部通信及日志。换算网络接口标称速率时,要先统一字节与bit、MiB与十进制单位。

将请求率分别换算为回源率和内容流量
将请求率分别换算为回源率和内容流量

如果上传全部保留,内容净增长按此负载约为25×3600=90000MiB/小时。真实系统通常不会全天保持峰值,应按保留周期和分时流量累加,并另留临时文件、失败上传和日志空间。后台校验若还要完整读取每个新文件,就会增加约25MiB/秒的逻辑读取需求;实际设备负载还受系统缓存和访问方式影响,不能只用相加结果断言磁盘一定足够。

动手先记录环境与空闲基线,再做低并发请求。以下命令在你有权限的测试主机上执行,127.0.0.1:8080代表已经启动的实验服务,示例文件应为专用测试数据。它们观察不同资源,并不自动构成完整压测。

df -h ./data
df -i ./data
ps -eo pid,comm,%cpu,%mem --sort=-%cpu
ss -s
curl --fail --silent --show-error --output /dev/null \
  --write-out 'status=%{http_code} total=%{time_total}s bytes=%{size_download}\n' \
  http://127.0.0.1:8080/files/sample

df -h检查文件所在文件系统的空间,df -i检查inode使用,ps提供进程资源快照,ss -s提供连接摘要。curl保留错误提示并输出状态、耗时与接收字节;退出码也要保留,失败结果不能混入成功耗时。单次本机请求只能检查路径是否通,不能证明远端网络吞吐或p99目标成立。

随后用允许设置并发、请求速率与持续时间的压测工具逐级增加负载,每级都记录到达量、成功量、延迟分位数和资源曲线。先测读取,再加入上传和后台任务;分别测热缓存与冷缓存。测试客户端也有CPU和网络上限,必要时从独立主机发起,并核对客户端发出速率与入口收到速率。若负载来源先饱和,测试结果不能当成服务上限。

最后将600次/秒回源与后端实测安全速率比较。若安全速率为800次/秒,当前占75%;但命中率降至80%时,回源变成1200次/秒,立即超出该能力。这个模型的价值是找出敏感假设,并据此安排冷启动测试、保护阈值和扩容条件,而不是用整齐的算式代替实际测量。

5 简单可靠与极致优化取舍

简单可靠的方案通常有较少的状态副本、清楚的成功语义和可操作的恢复步骤。例如先限制文件大小、分开后台并发、增加有限缓存,可能已经满足目标。极致优化则可能引入更多缓存层、分区和任务转发。它们有合理用途,但也增加了故障定位、配置管理和数据一致性的负担。评价方案时,不能只比较理想负载下的吞吐。

缓存大小与收益不是固定比例。增加内存可能容纳更多常用对象,却无法保证相同幅度的命中率提升;热点变化、对象大小和过期时间都会改变结果。过期时间延长能减少回源,同时延长旧数据存在的时间。若配置文件必须迅速生效,就应优先选择可验证的版本或失效机制,而不是仅为了漂亮的命中率延长有效期。

拖动交互时,关注回源预算何时被超过,以及内容出站为何保持不变。这里假设6000个读请求全部成功,平均响应体固定。它并未模拟错误、重试或真实延迟,因此不能从滑块直接读出p99。实际命中率下降往往伴随回源排队,还需要用压测验证入口保护能否守住合法请求的体验。

可靠性也需要资源预算。假设读服务由2个相同实例承担,正常各处理3000次/秒;要求其中1个停止后继续满足6000次/秒,就必须证明剩余实例及共享后端能够承受全部负载。仅把进程复制成2份,并不保证文件系统、网络出口或元数据服务也有相应余量。共同依赖仍可能成为故障集中点。

优先选择团队能够观察、维护和回退的设计。若某项优化只改善极少数请求,却让日常部署必须协调多个组件,应把收益与运维成本一起呈现。可以先设定触发条件,例如回源持续接近规划预算且优化访问方式仍不达标,再考虑增加实例或进一步拆分。把暂不采用的复杂方案及其触发条件写清楚,也是设计能力。

6 常见坑:过早引入复杂中间件

常见误区是先画出很大的组件图,再寻找每个组件的用途。缓存、消息队列和多个服务都可能合理,但每增加1个组件,就增加连接、配置、权限、监控与失败状态。对于请求量不高的文件服务,文件描述符泄漏、未限制的上传体积或错误的重试循环,可能比是否采用复杂中间件更紧迫。先确认瓶颈,才能选对改动。

另1个坑是每层都有重试却没有总预算。客户端超时后重试,入口再重试,后端调用也重试,同1项工作可能被重复执行多次。超时表示调用者没有及时得到结果,不等于写入一定失败。应传递请求标识,限制重试次数与总耗时,并让重复提交能够被识别。不能把重试当成没有成本的可靠性开关。

无限队列与无限并发也常被误认为能“扛住突发”。实际上它们会占用内存、句柄和连接,把快速拒绝变成长时间等待。对允许等待的工作设置有界队列,对读回源设置并发上限,并明确超限响应。限制应根据服务能力测试确定;过小会浪费资源,过大则可能在过载时让整个进程失去响应。

把请求表现与进程、文件系统、连接证据对齐
把请求表现与进程、文件系统、连接证据对齐

只看CPU同样容易误判。CPU低可能是进程等待文件读取或慢客户端;磁盘空间充足也可能inode不足;连接数增长可能来自正常长传输,也可能来自请求无法完成。可以用vmstat 1观察系统运行与等待趋势,用ss -tan查看TCP连接状态分布,再结合进程日志判断。命令提供线索,不会独立证明根因,且部分字段含义需按本机工具说明解释。

观测需要控制自身成本。为每个请求记录完整文件内容会增加I/O,还会泄露敏感数据。通常记录请求标识、接口类别、状态、耗时、响应大小、缓存结果和回源耗时就能支持初步分析。避免直接将每个对象标识做成监控指标标签,否则指标数量可能持续膨胀。细节留在有权限控制和保留期限的日志中,聚合指标用于看趋势。

7 案例:缓存穿透打爆后端

文件服务上线后,正常命中率接近90%,回源维持在预算内。某次客户端脚本把目录名误当成对象标识,持续请求不存在的文件。缓存中没有这些对象,每次都进入后端检查,再返回不存在。入口总QPS变化不大,合法读取却明显变慢。这类不存在对象请求不断穿过缓存、消耗后端能力的现象,称为缓存穿透。

先保存同一时间窗口的证据:按接口与状态码统计速率,查看缓存未命中量、后端查询量、回源等待时间和合法对象的p99。若不存在响应增加、回源增加、队列增长同时出现,就有了值得验证的假设;仍需检查发布变更、缓存重启与文件权限错误,避免把所有未命中都归因于不存在对象。权限拒绝不能草率当成全局不存在来缓存。

恢复时先保护后端:限制回源并发,对异常来源或超出预算的请求执行速率限制,必要时暂停有问题的脚本。对重复访问的不存在对象,可以短期缓存“不存在”的结果,称为负缓存;其键空间与占用也必须有上限。若攻击或错误脚本每次产生新的随机标识,负缓存命中很少,仍需入口保护,不能把负缓存当成完整防线。

负缓存还有更新责任。某个标识刚才不存在,随后上传成功,旧的负缓存可能让新文件暂时不可见。方案可以在发布时使对应记录失效,或使用足够短且被业务接受的有效期,并验证最坏可见延迟。路径格式检查能提前拒绝非法标识,却无法证明格式正确的对象一定存在。每项措施都应写清适用范围。

从尾延迟异常走向可验证的穿透假设
从尾延迟异常走向可验证的穿透假设

恢复验收不能只看后端CPU下降。应确认合法请求成功率与p99恢复、队列能够回落、拒绝量可解释,并测试新建对象能及时被读取。随后回放经过脱敏和限量的故障负载,比较措施前后的回源量。复盘应修正容量模型:除正常命中率,还要加入不存在对象比例和随机键场景,把故障暴露的假设变成可复测条件。

8 把容量模型写进架构方案

容量模型应成为方案正文中的证据链。先列负载假设:读写峰值、文件大小分布、热点程度、保留时间、后台任务速率、增长预期与允许的陈旧时间。每项数字注明来源,是已有观测、产品约束还是待验证估计。估计不是错误,隐藏估计才会让后续讨论失去依据;重要假设应列入测试计划。

随后按路径给出资源预算。入口负责接收与保护,读进程负责权限检查和内容发送,后端负责回源,后台进程负责任务完成。分别说明正常负载、冷缓存和实例故障条件下的预期需求。容量不足时的动作也要明确:拒绝新上传、降低后台并发、扩充读实例或增加存储空间,各自解决的问题并不相同。

架构方案中从假设到验收的闭环
架构方案中从假设到验收的闭环

接口约定应与模型连接。例如最大文件体积限制网络与缓冲区消耗,上传超时限制连接占用,任务状态接口说明异步完成的可见性,回源上限约束后端排队。错误响应应允许客户端区分无权限、不存在、过载与暂时失败,同时避免泄露不应公开的对象信息。状态码与返回正文需要形成稳定契约。

验收计划要覆盖正常读写、冷启动、后台积压、文件系统空间接近阈值和后端变慢。演练应在受控环境进行,并设停止条件,避免把容量实验变成无边界的压力攻击。观测记录使用统一时间范围,保留版本、参数与负载分布,使另1位同事可以复现结论。上线后按真实数据修正模型,架构图才不会成为与运行状态脱节的装饰。

9 从大规模系统设计走向网络安全进阶(S59)

容量保护与安全保护有交集。错误脚本和恶意客户端都可能制造大量连接、随机对象查询或超大上传;服务必须在身份、权限、请求大小和速率边界上约束消耗。不过,限流并不代替授权,缓存命中也不意味着可以跳过权限判断。系统能够承受请求,只说明资源尚有余量,不说明调用者有权获得数据。

设计中应先识别信任边界:哪些接口对外开放,哪些后台进程能够读取原始文件,谁能修改配置与日志。服务进程使用所需的最小文件权限,shell脚本正确引用输入,并避免把外部标识直接拼成任意文件路径。文件标识到实际路径的映射、日志中的敏感字段和后台任务凭据,都值得在进入安全学习前标注出来。

慢连接说明了为什么资源保护不能只按QPS设置。假设1000个下载各自保留256KiB应用缓冲区,仅缓冲区就占用250MiB,还未计入内核套接字内存和文件句柄。客户端迟迟不读取时,TCP发送缓冲区会逐渐填满,应用写入需要等待可写事件;若服务仍持续预读文件,数据就会堆在应用内存中。应让读取速度受发送进度约束,并限制每连接缓冲区及活跃传输总数。使用非阻塞I/O能够减少线程等待,却不会自动消除这些占用。

超时也要区分无进展时间和总传输时间。客户端每隔几秒接收少量字节,可能一直避开空闲超时,却长期占据下载名额;只设很短的总超时,又会误伤允许的大文件传输。方案应结合文件大小与最低可接受传输速率给出期限,并在断开后取消后续读取、释放句柄。演练时固定慢连接数量,观察应用缓冲区是否稳定、连接关闭后句柄是否回落,以及并行小文件读取是否仍达标,才能证明隔离有效。

S59将继续讨论网络安全进阶。本课先保留可迁移的提问方式:请求从哪里进入,经过哪些检查,消耗什么资源,失败时留下什么证据。面对异常流量时,先用状态码、连接与资源数据区分现象,再依据授权范围采取措施;不要从某个高QPS数字直接断言遭到攻击,也不要因来源在内网就忽略访问控制。

放进方案里

在架构方案中增加“文件读写容量与保护”条目,先说明本期服务提供按对象标识读取、上传及后台校验状态查询。规定文件大小上限、权限检查位置、上传成功的持久化承诺和异步任务完成语义,并写明暂不支持的场景。使用同一版本的接口约定,避免客户端把已接收误认为校验已完成。

把负载模型写成可复算的段落:峰值读6000次/秒、写200次/秒,平均读取32KiB、上传128KiB,正常命中率90%。据此得到读回源600次/秒、响应内容出站187.5MiB/秒和上传内容入站25MiB/秒。注明这些数值的来源、统计窗口及待验证假设,另列协议流量、后台读取、日志与保留空间,不用平均文件大小代替完整分布。

为各条路径列出实测能力和规划预算。示例后端安全回源速率800次/秒,规划使用640次/秒;命中率下降、冷启动和实例退出时重新核算。配置回源并发上限、有界任务队列、上传限制和超时预算,明确过载时拒绝什么、客户端如何退避。负缓存注明有效期、占用上限及新对象发布后的失效行为。

验收同时检查成功率、p99、回源量、队列趋势与资源余量,覆盖正常负载、随机不存在对象、慢客户端及后台积压。记录测试环境、停止条件和结果保存位置。上线后由明确负责人观察阈值、执行扩容或回退,并在负载分布变化后更新模型。方案评审应能沿着“假设—计算—实测—保护—恢复”复查每个结论。

补充慢连接验收项:记录活跃下载上限、单连接缓冲区预算及按文件大小确定的传输期限。在约定数量的慢下载并行时,小文件读取p99仍须不超过200ms;超时断开后,文件句柄与缓冲区占用应在约定回收窗口内回落。保存连接数、进程内存和句柄数的同期曲线,若无法达标,应缩小并发边界后重新验证。

10 小结:下一步网络安全进阶(S59)

大规模系统设计从明确负载与成功语义开始,再把请求换算成进程、文件系统和网络资源需求。拆分、缓存与异步只有对应具体瓶颈,并配套失败处理和观测,才构成完整方案。

自测:命中率从90%降到80%时,6000次/秒读取会产生多少回源?为什么缓存命中并不减少向客户端发送的内容字节?上传超时后直接重试会带来什么风险?若你能结合路径、预算与证据回答,就可以带着这些边界进入S59,继续讨论怎样让系统面对不可信请求时仍然安全、可控。

同阶段相关课

AI 答疑

默认关闭。启用后,只发送本课标题、最近阅读的屏幕内正文摘录(最多 6000 字)与本次问题。

配置自己的服务