一个请求,
到底怎么跑起来的?

从 Service、REST API 和 Latency,一路看到 Concurrency、Throughput 与 Queueing。

目标不是背名词,而是让你看到 dashboard 时,脑子里能出现一张正在流动的系统图。v0.2 新增:超时重试、熔断降级、容量规划、可观测性——系统变大之后真正会遇到的四件事。

开始跑第一个 Request
C Client
A Order API
S Order Service
request → processing → response
01

系统实验室

左边是同一个系统。右边每往下一步,只新增一个必要概念。

LIVE MODEL
Service 是什么?
Incoming 24 rps
Throughput 24 rps
Latency 180 ms
Waiting 0
从这里开始

Service 到底是什么?

先把它理解成一个持续运行、负责某类能力的程序。比如 Order Service 负责订单规则,Payment Service 负责付款。

为什么系统会拆成很多 service?

Service 和 server 是一回事吗?

两个 service 为什么需要互相通信?

先记住:Service 是“干活的单元”,通信方式是另一件事。
通信窗口

为什么大家总说“这个 service 有个 API”?

因为一个 service 通常不会允许别的程序直接碰它的内部状态,而会暴露一个明确的 interface:你能让我做什么、需要给我什么、我会返回什么。

GET /orders/:id POST /orders POST /orders/:id/cancel

REST API 是一种常见的 HTTP request / response 风格,但 service 本身并不等于 REST API。

API 像 service 的办事窗口;REST API 是窗口的一种设计方式。
第一次调用

一个 Request 进去以后发生什么?

Client 发 request,service 可能做业务逻辑、查数据库、调用下游,然后返回 response。

  1. 01 Client 发出 HTTP request
  2. 02 API 接收并校验
  3. 03 Service 做实际工作
  4. 04 Response 回到 client
这一整趟旅程,给了我们第一个性能问题:它花了多久?
时间视角

Latency:慢,到底慢在哪?

Client 看到的 500 ms,可能不是 service 自己算了 500 ms。它包含网络、等待、应用逻辑、数据库、下游调用等。

network waiting app db downstream
Latency 是时间。它回答“一个 request 要等多久”,不是“一秒能处理多少”。
从一个变很多

Concurrency:一个 request 没结束,还能接下一个吗?

通常可以。系统可以同时有很多 request 正在执行、等数据库、等网络。Concurrency 描述同时在场的工作量。

1 个 request = 500 ms 最多 2 rps
只要系统能并行维持多个 request,单请求很慢也不代表吞吐一定低。
核心概念

Throughput:单位时间真正完成了多少工作?

在 REST API 里,经常用 requests per second(RPS)描述。它和 traffic、latency、capacity 相关,但不是同一个东西。

concurrency throughput × latency

这是 Little's Law 在稳定系统里的直觉化写法;先拿它理解关系,不需要背推导。

拖左边三个旋钮:你会看到 throughput 不是孤立数字,而是系统状态的结果。
接近极限

Queueing:进来的速度,比处理的速度快了

这里说的 queueing 只是“等待”这个现象,不是说你用了 RabbitMQ 或 Kafka。

Request 会在哪里等?

为什么接近 capacity 时 latency 会突然上升?

为什么 CPU 没到 100% 也可能已经卡住?

把 Incoming traffic 调到很高:先涨的是 waiting,再涨的是 latency;throughput 最终会被 capacity 卡住。
02

然后问题才开始分叉

不是 API 不够强,而是不同工作对“谁要等谁、结果什么时候要、消息是否要保留”有不同要求。

Request / Response

“现在帮我做,并把结果告诉我。”

REST API / gRPC 适合 caller 需要明确结果的同步交互。

Work Queue

“这件事请处理,我不用一直等着。”

SQS / RabbitMQ 适合后台任务、削峰、任务重试与 worker 分工。

Event Stream

“系统刚刚发生了这件事。”

Kafka 适合保留事件历史、多个独立 consumer 与 replay。

下一轮会把三种模式放进同一个业务场景,比较 latency、throughput、failure、consistency 和 operational cost,而不是做“技术升级排行”。

03

先预测,再揭晓

如果一个系统最多稳定完成约 80 rps,但现在持续收到 120 rps,会最先看到什么?

05

超时、重试与幂等

请求发出去没回音,鸭鸭的第一反应是:再发一次!很勇,但也很危险——重试会把失败率变成流量的放大器。先看一个小算术。

重试放大器:拖一拖,看下游实际收到多少
客户端发出
100 rps
下游实际收到
139 rps

算法:100 × (1 + 0.30 + 0.30²) ≈ 139 rps,多出来的全是重试。

记住这个直觉:下游越惨(失败率越高),重试喂给它的流量越多——这就是重试风暴。解法三件套:超时设上限、重试加退避和抖动、关键操作配幂等键。
幂等键:连点两次下单,会扣几次款?

网络抖了一下,你的手指也抖了一下。试试两种下单方式,各点一次“连点两次”:

订单数0
扣款次数0

幂等键 = 给这次操作发一个唯一身份证号(如下单时带的 request-id)。服务端见过这个号,就直接返回上次的结果,不再干活。

能重试的操作才敢重试。下单、扣款这类操作,先让它幂等,再谈重试。

下游已经过载、开始大量超时,此时客户端无脑重试,最可能发生什么?

06

熔断与降级

下游已经挂了,还要每个请求都傻等超时吗?鸭鸭的选择是:fail fast——明知打不通,就别排队等了,直接返回兜底结果,把力气留给还能活的部分。

熔断器模拟器:把它玩到跳闸
下游状态 CLOSED · 正常通行
  1. 熔断器就绪。先发一波请求,再把下游切到“挂掉”试试。
熔断三态:CLOSED 正常通行 → 失败太多跳到 OPEN(直接拒绝、快速失败)→ 过一会儿放一个试探请求(HALF-OPEN),成了就关回去。舱壁模式是同一个思想的空间版:给核心能力和边缘能力分开的“船舱”,推荐挂了也别淹了下单。
下单池(核心)
独立线程池,照常接单
推荐池(边缘)
下游挂了,只淹这一个舱 → 降级为默认推荐

熔断器处于 OPEN 状态时,面对新请求最好的行为是?

07

容量规划

压测报告说“单机 500 rps”,老板问“大促要几台机器”。鸭鸭的换算直觉:永远不要按 100% 利用率买机器,留出水位,才能接住抖动和重试。

机器数换算器:从压测数字到机器数
机器数 = 峰值 ÷ (单机容量 × 利用率) 6 台

2000 ÷ (500 × 70%) = 5.7 → 向上取整 6 台。多出来的 0.3 台就是你的缓冲水位。

三步走:压测出单机安全容量 → 按目标利用率打折 → 向上取整再加 N+1 冗余。顺便把自动扩缩容的触发线画在利用率到顶之前,而不是之后。

单机压测 500 rps,目标利用率 70%,预期峰值 2000 rps,至少准备几台?

08

可观测性入门

“平均延迟 120ms”听起来很健康——直到你发现每 100 个用户里就有 1 个等了 2 秒。鸭鸭只记一句话:看 p50 / p95 / p99,不看平均值

分位数演示器:点一下,模拟 100 次请求
平均值
p50
p95
p99

p99 = 99% 的请求都比它快。平均值会被少数巨大离群值拽走,p99 会告诉你“最倒霉的那批用户到底有多倒霉”。

定规矩要定在分位数上:“p99 < 500ms”比“平均 < 200ms”更能保护真实用户。顺手把 RED(Rate / Errors / Duration)三个图钉在 dashboard 第一屏。

接口平均延迟 120ms,但 p99 是 2s。这意味着?

09

最后只留下四个问题

学完超时、熔断、容量和分位数,再回头看这四个问题——它们还是够用的,只是你现在每个问题后面都多了一套工具。

01

多快进来?

arrival rate / traffic

02

多快处理?

throughput / capacity

03

在哪里等待?

queueing / latency

04

谁卡住了?

bottleneck