工程架构认知一:一次请求到大量请求

张开发
2026/4/11 23:36:35 15 分钟阅读

分享文章

工程架构认知一:一次请求到大量请求
从“接口能用”到“系统可控”工程架构认知的第一步很多开发者对系统的直觉停留在这样一个模型写接口 - 查数据库 - 返回数据这个模型足够你把功能做出来但不够你解释下面这些真实问题为什么第一次请求总是更慢为什么平均延迟不高用户还是觉得卡为什么数据库 CPU 不高接口却频繁超时为什么机器资源看起来没打满系统吞吐却上不去架构视角真正关注的不是“代码有没有执行”而是一个请求在系统里经过了哪些阶段、占用了哪些资源、在哪些地方等待、问题又会被怎样放大。把一次请求拆开你会发现它更像下面这条链路请求到来 - 网络传输 - 网关转发 - 排队等待资源 - 获取线程/连接 - 执行业务逻辑 - 访问缓存/数据库/下游服务 - 释放资源 - 返回响应注意这里面最容易被忽略的不是“执行”而是“等待”。很多慢请求不是算得慢而是排得久、等得久、重试得多。这篇文章聚焦五个最核心的问题什么是延迟预算为什么性能优化首先是分配预算冷启动和热路径为什么会让同一个接口表现完全不同连接池到底在解决什么它为什么本质上是并发控制常见链路的延迟应该具备怎样的量级直觉当一个接口变慢时应该按什么顺序拆解一、延迟预算性能不是越快越好而是必须可分配、可控制1.1 什么是延迟预算延迟预算Latency Budget指的是一个请求从发出到完成允许消耗的总时间上限。例如页面要求 300ms 内返回首屏数据API SLA 要求 200ms 内响应内部 RPC 约束 50ms 内完成这个上限不是“理想值”而是系统设计的硬约束。只要其中某一层超支整体就会超时。1.2 延迟不是一个点而是一条链路一次请求的耗时从来不是单点而是多个阶段叠加前端 - 网络 - 网关 - 服务 - 缓存/数据库 - 返回如果你的总预算是 200ms那么就必须显式拆账而不是拍脑袋优化阶段建议预算网络往返30 ~ 50ms网关/代理2 ~ 5ms服务本身逻辑10 ~ 30ms缓存/数据库20 ~ 80ms预留抖动空间20 ~ 30ms所谓“预算”核心就在于分配。架构层关心的不是某一层能不能做到极致快而是整条链路能不能稳定地不超支。1.3 为什么只看平均值会误导你很多系统的平均响应时间其实不差问题出在尾延迟。例如一个接口平均值 40msP50 25msP95 120msP99 700ms这意味着大部分请求都很快但有少量请求会极慢。用户真正抱怨的通常不是平均值而是这些偶发但频繁感知到的慢请求。所以性能分析不能只看平均值至少要看P50大多数请求的中位水平P95系统是否开始出现抖动P99是否有严重的排队、锁竞争、GC、冷启动、下游抖动从架构视角看尾延迟比平均值更重要因为它更接近系统的失控边缘。二、冷启动与热路径为什么“同一个接口”能差一个数量级2.1 冷启动不是单一事件而是一组“未准备成本”冷启动Cold Start的本质是系统从未就绪状态切换到可处理请求状态所付出的额外成本。它往往不只是一件事而是很多“小成本”叠加起来TCP 建连TLS 握手应用实例启动ORM 初始化连接池首次建连缓存未命中数据页未进入内存所以第一次请求慢往往不是某一个点出问题而是整条链路都还没热起来。2.2 冷启动常见发生在哪几层可以把冷启动拆成四层来看层次常见现象典型后果连接层TCP/TLS/数据库建连首次请求多几十到几百毫秒服务层容器拉起、运行时初始化、类加载实例刚起来时明显变慢应用层配置读取、ORM metadata、缓存对象构建第一个业务请求被初始化拖慢数据层缓存未命中、磁盘读、索引页未热SQL 第一次很慢后面明显变快2.3 热路径是什么热路径Warm/Hot Path指的是系统已经进入稳定态请求可以沿着“已准备完成”的路径直接执行。热路径通常具备这些特征连接已经建立可以复用依赖实例已经启动完毕缓存已经有数据或数据页已在内存运行时已经完成部分优化常用对象和配置已经装载完毕冷路径和热路径的本质差异不在“代码逻辑”变了而在“准备成本”是否已经提前支付。2.4 为什么第一次慢、后面快、空闲后又慢这是典型的冷启动表现第一次请求慢因为系统还没准备好后续请求快因为连接、缓存、运行时都进入了热态空闲一段时间后又慢因为连接可能被释放缓存可能失效实例也可能被回收如果一个接口第一次 500ms后续稳定在 50ms优先怀疑连接懒加载应用初始化缓存未热数据库页缓存未命中而不是一上来就判断“SQL 很慢”。2.5 工程上如何降低冷启动影响常见做法包括服务启动时预热关键依赖而不是等第一次请求触发对连接池做预建连避免首个真实用户承担初始化成本对热点接口做缓存预热对弹性实例设置最小保留实例数避免实例频繁缩容回收把特别重的初始化逻辑从请求路径上移走一句话总结冷启动不是不能接受但不能让真实用户无条件替你买单。三、连接池它不是“连接集合”而是资源调度器3.1 连接池真正解决的是什么问题很多人以为连接池只是为了“省掉建连时间”。这只说对了一半。连接池本质上同时解决三件事复用昂贵连接减少重复建连成本限制并发访问保护数据库或下游服务提供排队和调度机制避免请求无限制打爆后端所以连接池不是一个被动容器而是一个主动的并发控制器。3.2 一个请求经过连接池时到底发生了什么真实流程通常是这样的请求到来 - 连接池是否有空闲连接 - 有直接复用 - 没有是否允许创建新连接 - 允许新建连接延迟上升 - 不允许进入等待队列延迟继续上升 - 等太久超时或失败这意味着接口总耗时并不只是“SQL 执行时间”而更准确地说是接口耗时 排队等待连接 获取连接 SQL执行 结果返回很多人盯着 SQL 只看到 20ms却忽略了前面已经等了 150ms。3.3 为什么连接池大小不是越大越好连接池太小会导致排队明显增加连接池太大也不一定更好因为数据库本身也有并发上限更多连接意味着更多上下文切换和锁竞争高并发下大量慢 SQL 会把数据库拖入整体退化换句话说连接池不是“放大吞吐量”的按钮而是“控制系统稳定边界”的阀门。一个常见误区是服务有 200 个并发请求就想把连接池开到 200。这样做的结果往往不是更快而是数据库被推到更高压力区尾延迟更差。3.4 为什么明明有连接池第一次请求还是慢通常有三个原因连接池是懒加载的启动时并没有真正建立连接ORM 或数据库驱动第一次执行时会做额外初始化数据层本身也是冷的连接热了不代表数据热了所以“有连接池”不等于“已经热好”。慢的不只是连接而是整个系统还没有进入稳定工作状态。四、性能问题往往不是“算得慢”而是“等得慢”4.1 一个慢请求通常只有三种来源把所有性能问题抽象一下慢请求大体只来自三类原因真正干活慢等待资源慢被放大后变慢对应到工程现场就是类型典型原因常见表现干活慢大查询、复杂计算、磁盘 IO单次执行时间本身就长等待慢连接池耗尽、线程池排队、锁竞争CPU 不高但延迟很高放大慢重试、缓存失效、链路过长、同步串行调用平均值上升P95/P99 更差其中最容易误判的是“等待慢”。因为它看起来像后端服务很闲但请求就是回不来。4.2 排队时间是最容易被低估的成本很多开发者分析延迟时只会拆网络 业务逻辑 数据库但在高并发系统里更真实的拆法应该是网络 排队 资源获取 业务逻辑 下游调用 返回一旦线程池、连接池、消息队列消费者、锁资源进入饱和系统的主要延迟就不再来自执行而来自等待。这也是为什么有时候你会遇到下面这种现象CPU 不高SQL 单次不算特别慢但接口响应时间越来越长其根因往往是请求已经开始排队系统正在从“可用”滑向“拥塞”。4.3 架构设计为什么要尽量缩短同步链路同步链路越长风险越大因为每增加一个下游都会引入额外网络耗时都会引入一个新的资源等待点都会把下游抖动传导到上游都可能触发重试形成放大效应一个接口如果串行依赖 5 个服务就算每个服务只增加 10ms看起来也不夸张但只要其中一个服务偶发抖动整条链路的尾延迟就会迅速恶化。所以架构优化很多时候不是“把某个函数写快”而是减少同步依赖缩短关键路径把非关键流程异步化尽量让热点请求直接命中缓存五、建立量级直觉不同链路的延迟大概应该在什么范围下面这些数字不是绝对标准但足够帮助你建立判断基线。5.1 常见延迟区间场景典型范围本机内存访问纳秒到微秒级本机进程内计算1ms到几毫秒Redis 内网访问0.2 ~ 2ms同机房服务调用1 ~ 5ms跨机房服务调用5 ~ 20ms普通数据库热查询1 ~ 20ms复杂数据库查询20 ~ 200ms新建数据库连接50 ~ 200ms公网一次 HTTP 请求20 ~ 100ms5.2 这些数字该怎么用这些数字的价值不在于背下来而在于帮助你快速判断“是否异常”。例如Redis 查询 8ms要开始怀疑网络抖动、阻塞或跨可用区简单 SQL 120ms要怀疑索引、锁、冷数据或连接等待同机房 RPC 30ms大概率已经不是单纯网络问题第一次 400ms、后续 40ms优先怀疑冷启动不要直接归因给业务逻辑架构认知的第一步不是学会所有原理而是先知道“什么是正常量级”。六、遇到慢接口时应该怎么拆6.1 先问四个问题当一个接口变慢时先不要急着进代码先回答这四个问题是一直慢还是第一次慢、偶尔慢是平均值上升还是只有 P95/P99 变差是执行时间长还是等待时间长是本服务变慢还是下游把问题传上来了这四个问题几乎决定了排查方向。6.2 一个实用的拆解顺序可以按照下面这个顺序来定位先看总耗时分布区分平均问题还是尾延迟问题再看调用链确定慢在网关、服务、缓存、数据库还是下游 RPC看资源等待重点查线程池、连接池、锁、队列长度看数据访问确认是否有慢 SQL、索引失效、热点竞争、缓存未命中看系统状态确认是否存在 GC、事件循环阻塞、实例冷启动、网络抖动这个顺序的价值在于先区分“执行慢”还是“等待慢”再决定要不要深入代码或 SQL。6.3 用一个例子理解“真正的慢点”假设一个接口总耗时 300ms链路拆开如下网络传输 20ms 网关处理 2ms 等待数据库连接 140ms SQL执行 25ms 业务逻辑 8ms 返回响应 20ms这时真正的问题不是数据库“执行得慢”而是数据库连接已经成为瓶颈导致大量请求在前面排队。如果你只优化 SQL把 25ms 降到 15ms整体也只改善 10ms但如果你解决的是连接等待和并发调度问题可能一下就能从 300ms 降到 120ms。这就是架构视角和代码视角最大的不同架构更关心瓶颈在哪里而不是哪段代码看起来最显眼。七、你应该形成的性能判断力当你对系统越来越熟悉后应该逐渐形成这样的直觉一个接口第一次 500ms、后面 50ms大概率是冷启动不是纯业务复杂Redis 一般不该有几十毫秒除非网络、阻塞或部署拓扑有问题简单查询不该频繁到百毫秒除非索引、锁、缓存、连接等待出了问题服务资源没打满但响应变差优先怀疑排队、池子、锁而不是先怀疑 CPU平均值正常但用户持续投诉卡顿要立刻看 P95/P99而不是继续盯平均值这些直觉本身就是架构能力的一部分。八、总结从“调用函数”升级到“控制资源与等待”很多人的原始模型是请求 - 调函数 - 返回数据但更接近真实系统的模型应该是请求 - 排队 - 获取资源 - 执行 - 释放资源 - 返回当你开始用这个模型思考问题时你会自然关注预算是不是被合理拆分了哪些延迟是执行产生的哪些是等待产生的请求走的是冷路径还是热路径连接池、线程池、缓存、数据库分别在承担什么角色一个问题会不会沿着链路被不断放大这就是工程架构认知的第一步。不是把功能做出来而是让系统的性能变得可解释、可判断、可控制。

更多文章