1 服务网格与 API 网关要回答什么
用户在购物页面按下“提交订单”,浏览器向订单 API 发出请求。订单服务检查身份,再访问库存服务和价格服务,最后把订单写入数据库。页面上只有 1 次操作,部署环境里却发生了多段通信:外部请求需要找到入口,内部服务需要找到彼此,失败时还要决定等待、重试还是返回错误。服务网格与 API 网关处理的是这些通信问题,但它们负责的范围并不相同。
API 网关通常接住进入某组 API 的请求,根据域名、路径、身份或配额等条件决定是否接收,以及转发给谁。例如,/orders 交给订单服务,/catalog 交给商品服务。它把入口规则集中起来,让客户端不必知道每个服务有多少副本、部署在哪个地址。这里的“集中”指规则和责任的集中,实际承载请求的网关仍可有多个实例。
服务网格把服务之间的通信策略交给基础设施处理。订单服务访问库存服务时,流量可能经过与工作负载关联的代理,由代理实施连接加密、服务身份校验、路由、超时以及遥测采集。代理可以伴随应用部署,也可以由节点或其他共享组件承载;本课用代理作为理解模型,不要求读者先掌握具体部署形态。

可以把 2 类组件分别对应到 2 个问题:谁允许外部请求进入哪组 API;服务之间的请求按什么规则传递。它们都可能提供路由、认证和流量控制,因此不能仅凭功能清单区分。真正需要确认的是策略作用在哪段链路、识别哪种身份、由谁维护,以及失败由谁报告。名称不同不等于责任天然清楚,名称相同也不保证配置语义相同。
部署网关或网格不会自动解决业务正确性。网关接受了请求,不代表用户有权查看任意订单;内部连接通过了身份校验,不代表扣款可以重复执行。业务授权、数据约束和事务结果仍需要应用与数据系统共同保证。基础设施帮助团队一致地处理通信行为,应用继续决定“这件事能不能做”和“做完意味着什么”。
学习本课后,应能画出请求经过的代理位置,解释超时与重试为何会相互影响,并把入口、身份、网络、数据和观测信息写入可执行的连通方案。先从少量服务和清楚的边界开始,比先选择功能最多的组件更有帮助。
2 南北向网关与东西向代理边界
“南北向”通常指跨越系统入口边界的通信,例如浏览器访问订单 API;“东西向”通常指系统内部服务之间的通信,例如订单服务查询库存。这是相对于选定边界的描述,不是地理方向。同 1 次访问在公司边界可能属于内部通信,在某个业务系统边界又可能属于外部调用,因此画图时必须先标明讨论的是哪层边界。
在本例中,入口网关接收外部 HTTPS 请求,校验用户携带的凭证,匹配订单 API 路由,然后把请求转给订单服务。订单服务再通过内部代理访问库存服务。入口处的用户身份回答“当前操作者是谁”,内部通信使用的工作负载身份回答“发起连接的是哪个服务”。两者可能同时存在,不能因为连接来自订单服务,就丢掉原始用户的权限限制。
例如,库存服务只允许订单服务预占库存,这是服务间访问控制;订单服务只允许用户取消自己尚未发货的订单,这是业务授权。网格可以帮助实施前者,应用需要负责后者。若需要传递用户上下文,应明确字段来源、可信边界和校验规则,避免把客户端随意提交的请求头当成内部可信身份。

网络连通也不等于访问许可。域名解析成功,只说明名称能得到地址;端口能建立连接,只说明某条网络路径可用;代理接受连接后,还可能因为证书身份、路由或授权策略拒绝请求。排查时可以依次确认名称、网络、连接安全、路由和应用响应,不能用“容器都在运行”推导出完整调用链已经畅通。
数据访问还要另画边界。订单服务可能通过数据库协议连接数据库,网格对该连接能够提供的能力取决于协议支持和部署方式。不能假设针对 HTTP 的路径匹配、状态码统计或请求重试可以直接套用到数据库连接。数据库账号权限、事务提交和备份恢复,也不会因为通信进入网格就自动完成。
从部署视角看,网关实例和内部代理属于实际转发请求的数据面,向它们分发配置与身份材料的组件属于控制面。控制面负责规则如何到达执行位置,数据面负责当前请求怎样经过规则。配置提交成功与所有实例都已采用配置之间可能存在时间差,所以方案中需要写明如何检查生效状态,不能只保存配置文件。
内部代理也不意味着所有请求必须绕到中央网关。若每次内部调用都经过同 1 个入口,会增加路径长度,还可能把内部故障集中到入口组件。常见做法是入口策略在入口实施,内部调用策略贴近服务通信实施;确需跨系统网关时,则把这条边界明确画出来,并说明新增的延迟与依赖。
3 流量策略、重试与熔断
流量策略首先决定“往哪走”。订单服务有稳定版本和新版本时,可以按小比例把请求送到新版本,并比较错误率与延迟。比例表达的是符合条件的请求如何分配,不一定对应用户比例。若需要同 1 位用户持续访问同 1 个版本,就要另行考虑会话或路由标记;不能把随机分流误认为业务会话保持。
超时决定“最多等多久”。这里至少有 2 种常见时间:建立连接允许等待的时间,以及等待请求完成的时间。连接很快建立,并不意味着服务很快返回。还要核对超时是否覆盖排队、重试和响应体传输;不同组件对相同名称可能采用不同计时起点。本课用总请求时间来讲预算,配置到产品时仍需查看该字段的实际语义。
假设客户端能等待 3 秒,入口总预算为 2.5 秒,订单服务整体处理预算为 2 秒。订单还需要查询库存、访问数据库并生成响应,就不能把全部 2 秒都交给库存调用。为依赖留出的预算必须同时容纳连接、处理、传输以及可能的重试,并为后续步骤保留余量。依赖并行调用时,预算关系也不同于简单相加,应按实际执行路径分析。
重试决定“失败后是否再试”。它适合处理部分短暂失败,但需要同时明确允许重试的错误、最大尝试次数、每次等待时间以及整个操作的截止时间。这里统一用“尝试次数”计数:最多尝试 3 次包含首次请求和最多 2 次重试。某些配置字段写的是“重试次数”,配置值便会不同,交接时应直接写出首次是否计入。
重试还受操作语义限制。查询商品通常比创建订单容易安全重试,但也不能仅凭 HTTP 方法判断。创建订单若允许重试,应让同 1 次业务操作携带稳定的幂等键,并在服务端把去重记录与业务结果可靠关联。换 1 个键再发送通常会被视为新操作;只在网关内存里记住最近请求,也不足以保证服务重启后不重复创建。
熔断决定“是否暂时停止向异常依赖继续发起尝试”。当失败达到某个条件,调用方可以快速失败,之后再通过少量探测判断是否恢复。它与超时不同:超时限制某次等待,熔断影响后续请求是否进入依赖。它也与限流不同:限流限制到达速率,并发限制约束同时进行的工作。它们可能组合使用,但需要分别定义触发条件和恢复方式。

先把无重试的正常路径讲清,再为确有价值的调用增加有限重试。若一次失败已经消耗大部分预算,就算还没有达到次数上限,也不应该启动无法在截止前完成的新尝试。退避和随机抖动能分散重试时间,却不能替代预算约束,也不能让已经超载的服务凭空获得处理能力。
4 动手:对照一次网关超时与网格超时
这次动手的目标是通过可控延迟分辨谁先结束等待。请使用隔离的练习环境,准备入口网关、订单服务和库存服务,保证订单查询接口会同步等待库存查询完成。库存练习接口应支持固定延迟,并在开始与结束时记录请求标识。固定延迟是练习夹具的能力,不应给正式业务接口增加可被任意调用的延迟开关。
下面是实验所需状态清单,属于跨实现的配置意图,不能直接当成某种网关或网格的配置文件。把它映射到所用组件的实际字段时,应先核对总超时、单次超时和重试字段的定义。实验中关闭客户端、网关、应用 SDK 和网格的自动重试,并保持库存只收到 1 次尝试,避免把多个变量混在一起。
| 项目 | 基线 | 对照 A | 对照 B |
|---|---|---|---|
| 库存固定处理时间 | 200 毫秒 | 1800 毫秒 | 1800 毫秒 |
| 订单到库存的网格超时 | 1200 毫秒 | 1200 毫秒 | 2200 毫秒 |
| 网关到订单的总超时 | 2500 毫秒 | 2500 毫秒 | 1000 毫秒 |
| 客户端总等待上限 | 4 秒 | 4 秒 | 4 秒 |
| 所有层的尝试次数 | 1 次 | 1 次 | 1 次 |
先运行基线,确认路由和身份已经允许这条调用链通过。发起 1 次查询并保存客户端耗时、入口访问日志、订单日志以及库存日志。可使用下面的命令格式,其中地址需替换为练习入口,查询接口需由练习服务提供;X-Request-ID 也需由入口按约定保留或重新生成,并在内部传播。
curl --max-time 4 -sS -o /dev/null \
-H 'X-Request-ID: c37-a-001' \
-w 'status=%{http_code} total=%{time_total}\n' \
'https://LAB_GATEWAY/orders/demo'
基线应成功返回,但总耗时通常大于库存的 200 毫秒,因为还包含入口处理、订单逻辑和网络传输。若基线失败,应先修好路由、连接和授权,不要继续用超时参数解释其他故障。每轮更换请求标识,确保能把新实验与旧日志分开,也避免缓存或幂等记录干扰结果。
在对照 A 中,库存需要约 1800 毫秒,内部代理只允许等待 1200 毫秒,因此预期先在订单到库存这一段出现超时。订单服务可能把依赖失败映射成自己的错误响应,也可能由代理直接生成错误,外部最终看到的状态码取决于映射约定。不能只凭 504 就宣称是入口超时;应找到内部代理的超时记录,再确认入口收到的是上游返回还是自己生成的响应。
在对照 B 中,入口只等 1000 毫秒,内部预算却为 2200 毫秒。预期入口先停止等待。库存是否继续运行到约 1800 毫秒,要看取消信号是否沿代理、订单服务和库存处理传播,以及业务代码是否响应取消。即使连接已断,数据库操作也可能已经开始。实验记录应把“客户端收到错误”和“内部工作停止”作为不同事件观察。

每轮至少记录配置生效时间、请求标识、哪层生成错误、入口耗时和库存实际尝试次数。计时会受调度与网络影响,不要求恰好落在整数毫秒上;判断重点是事件顺序与预算关系。完成后恢复练习环境配置,并保留预期与实际的差异说明。若代理采用不同的计时语义,应先修正实验解释,再决定是否修改超时值。
5 可观测性与策略下沉取舍
策略下沉是把原本散落在各个应用中的通用通信规则交给网关或代理实施。好处是多个语言、多个团队的服务可以获得较一致的连接安全和基础遥测,也能减少重复实现。代价是请求多经过了需要配置、升级和排查的组件。规则执行位置离业务代码更远后,应用开发者必须知道去哪里寻找代理生成的失败证据。
指标、日志和追踪各自回答不同问题。指标适合观察某条路由的请求速率、错误比例和延迟分布;日志适合解释某次访问为何被拒绝或超时;追踪把入口与下游调用串成时间关系。入口指标显示错误增加,只能指出用户体验变差,仍需要内部调用的观测结果区分库存变慢、网络连接失败和规则误配。

请求标识帮助关联记录,追踪上下文帮助建立父子调用关系,它们的格式与用途可以不同。若应用没有正确传递上下文,代理采集到的片段可能无法拼成完整链路。也不要把追踪图中的空白直接理解为某段没有耗时:采样、埋点缺失和上下文中断都可能让部分调用不可见,需要与访问日志交叉核对。
按请求记录尝试次数尤其重要。入口看到 1 次请求,库存却处理了 3 次,若只有入口请求量,重试的内部成本就会被隐藏。应尽量区分逻辑请求与实际尝试,并观察在途请求、超时来源和依赖错误。高延迟还应查看百分位值及样本规模,平均值可能掩盖少量特别慢、却足以影响用户的请求。
把策略放在哪里,要结合变化频率和业务语义。连接加密、工作负载身份及通用遥测适合统一处理;是否允许对某类订单重试、失败后能否返回缓存价格,则需要业务团队参与。网关可以验证凭证格式和基本声明,应用仍要检查订单归属。让每层各做自己能够正确解释的决策,比把所有判断都搬到代理更稳妥。
引入网格的收益也应与维护能力匹配。只有少量服务、通信规则简单时,可以先用清晰的入口、成熟客户端和基本观测满足需求;服务和团队增多、策略一致性成为明显问题时,再评估网格。评估中要计入代理资源、配置发布、身份材料轮换、故障排查和升级演练,而不能只统计应用少写了多少代码。
日志标签也要克制。把订单号和用户标识放入每条指标标签,可能产生大量不同时间序列;记录完整令牌又可能暴露凭证。适合聚合的标签可以是路由模板、服务名和错误类别,具体请求标识留给受控日志与追踪。可观测性本身也消耗资源,应同时定义采样、保留期限和访问权限。
6 常见坑:网格重试放大故障
最容易被忽略的重试来源不只有网格。浏览器或移动端可能自动重发,网关可能对上游失败重试,订单应用的客户端库也可能重试,内部代理还可能再试。每层单独看都只增加少量尝试,组合后却可能把 1 次用户操作变成多次实际调用。故障期间,新增流量往往恰好压向最缺少处理能力的依赖。
假设入口对订单最多尝试 3 次,订单每次调用库存时,内部代理又最多尝试 3 次。若每轮都走到库存且满足重试条件,库存最多收到 3 × 3 = 9 次尝试。这个上限有前提:总预算足够、各次确实失败并触发后续尝试。它不是正常情况下固定发生的次数,也没有计入客户端或应用 SDK 的额外重试。
若入口每秒有 100 个这样的逻辑请求,极端情况下便可能产生每秒 900 次库存尝试。库存变慢后,在途请求增加、连接被占用,更多请求超时并重试,形成反馈。单纯增加超时可能让请求占住资源更久;单纯增加副本也未必解决共享数据库瓶颈。因此先确认放大来自哪里,再选择容量、并发或失败处理策略。
治理时,先画出每层的尝试上限,并为同 1 个远程操作明确主要重试责任。入口转发订单请求若没有必要重试,就保持 1 次尝试;库存查询的有限重试可以由了解调用结果的一层负责。若确有理由保留多层重试,必须计算组合上限并共享总预算,同时限制并发,避免各层只按自己的局部目标配置。
对明显不适合重试的失败应快速结束。例如,身份凭证无效通常不会因为立即再发就成功;库存业务返回“数量不足”也不同于临时网络失败。即使错误看起来可以重试,还要判断服务是否已经执行了副作用。超时只说明调用方没有按时获得结果,不能证明订单没有创建,更不能作为重复扣款的许可。
熔断与退避可以减轻重复压力,但仍需要观测验证。观察失败期间的实际尝试数量、在途数量和恢复后的成功率,确认保护策略是否真正减少了依赖负担。恢复阶段也应避免所有调用方同时集中探测。入门阶段不需要钻研复杂算法,先做到次数有上限、时间有预算、写操作有幂等约定、失败有证据。
7 案例:双层超时互相覆盖
某订单查询原本在约 600 毫秒内完成。一次库存维护使库存查询变为约 1800 毫秒,用户开始看到偶发失败。入口团队看到网关日志中的超时,把入口预算从 1000 毫秒提高到 2500 毫秒,期待请求等待得更久。随后外部失败依然存在,只是返回时间变为约 1200 毫秒,状态码也与之前不同。
真正的路径是:入口原先在约 1000 毫秒结束等待,掩盖了内部代理在 1200 毫秒设置的超时;入口放宽后,内部代理先到期,订单服务把依赖错误转换为自身的失败响应。外部观察到的现象变了,库存处理时间却没有改善。“互相覆盖”指较早生效的限制遮住了另一个限制,并不是某个配置值真的覆盖写入了另一份配置。
团队先选取失败请求标识,把入口、订单和库存记录连起来。在旧配置下,入口标明自己生成了超时响应,库存端还可能继续处理。在新配置下,入口记录的是上游较早返回的错误,订单日志指向库存依赖超时。只有把错误生成位置和时间对应起来,才能解释为什么修改入口参数后仍然失败。

继续调查发现,订单页面并非每次都需要实时精确库存,但提交订单时必须执行可靠的库存预占。于是团队把查询与提交分开评估:查询允许按明确规则使用短期缓存,并标明数据时效;提交仍走受控的库存操作,并保持幂等约定。是否接受缓存由业务决定,代理不能仅因依赖超时就擅自返回可能失真的业务结果。
配置修复也不是把所有超时都改成更大的相同数字。团队先确认页面可接受的总等待时间,再为订单处理和库存依赖划分预算,给响应传回入口留出余量。若库存持续需要超过业务可接受时间,就应优化依赖或调整交互方式,而不是让用户等待的时间无限增长。入口预算应能容纳合理的内部失败响应,但仍不能超过用户侧的目标。
复测包含正常返回、内部先超时和入口先超时这 3 类路径,并核对实际尝试次数。团队另外确认了取消传播:对于无法及时取消的内部任务,记录结束状态并控制并发;对于已经完成的写入,保留可查询的业务结果。这样客户端再次查询时,可以分清操作失败、结果未知和实际已经成功,减少盲目重发。
这个案例的可迁移结论是:外部错误往往只反映最先到达的限制。调整较外层预算之前,应先列出整条链路上的时间限制和生成错误的组件;调整之后,还要确认新的失败位置是否符合预期。让现象可解释,才能判断修改是在改善服务,还是仅仅把另一个已有问题暴露出来。
8 把流量入口写进连通方案
连通方案要让部署、应用和运维人员对同 1 条路径有一致理解。先写访问方从哪个域名进入、经过什么边界、访问哪类 API,以及由谁拥有路由规则。例如,外部客户端通过订单域名访问入口,入口按 /orders 路由到订单服务,订单再通过内部服务名查询库存,最后由订单服务使用受限账号访问数据库。
随后把身份和网络分别写清。入口校验用户凭证,订单执行订单归属与状态授权,内部代理验证工作负载身份,数据库校验应用账号。网络侧说明哪些入口可被外部访问、哪些内部端口允许服务间连通,以及数据库仅接受哪些来源。用户身份、服务身份和网络来源是互相补充的证据,不能在表格里合并成含糊的“已鉴权”。
| 方案字段 | 必须能回答的问题 |
|---|---|
| 入口与路由 | 哪个域名、哪段路径进入哪个服务,规则归谁维护? |
| 身份与授权 | 哪层验证用户,哪层验证服务,业务权限在哪里判断? |
| 时间预算 | 每段从何时计时,何时结束,谁生成超时响应? |
| 尝试与副作用 | 哪层可以重试,最多尝试几次,如何避免重复写入? |
| 观测与验收 | 用什么标识关联记录,怎样证明策略已生效? |
对每项策略,尽量同时记录意图和执行位置。“库存调用最多尝试 2 次,由订单侧代理执行,入口与应用 SDK 不再重试”,比“开启重试”更容易验收。“网关总预算 2500 毫秒”也需要补充是否包含连接等待和重试,而不能只写 1 个数字。具体数值来自业务目标与测量结果,本课示例只用于展示关系。
配置发布要进入方案。路由、证书和超时策略都有可能发生误配,因此需要说明先在哪个练习或小范围环境验证、如何观察生效实例、出现异常如何恢复上一版规则。配置文件通过检查只能证明格式可能正确,不能代替实际请求验证;多个代理版本短暂共存时,更要关注实际行为是否一致。
最后写验收证据:正常查询能够成功,无效身份在预期位置被拒绝,指定延迟能触发预期超时,实际尝试次数符合上限,入口错误能够关联到内部记录。每项证据都应指向具体请求或聚合指标,并有负责处理异常的人。这样“网络已连通”就不再只是端口测试,而成为包含权限、行为和排障责任的完整约定。
9 从服务网格与 API 网关走向消息与事件驱动(C38)
网关与网格主要帮助我们管理请求怎样到达服务、服务怎样相互调用。同步调用的特点是调用方通常要等到被调用方返回,才决定下一步。订单页面查询当前订单很适合这种方式,因为用户此刻就需要答案;但订单创建后发送通知、更新统计等工作,未必都需要挡在当前响应前面。
假设订单已经可靠写入,随后还要通知用户并更新报表。若订单接口等待所有后续服务完成,任何后续依赖变慢都可能增加提交延迟。即使为这些调用加入代理、超时和重试,同步等待关系仍然存在。流量基础设施能管理通信,却不会自动改变业务步骤之间必须等待还是可以稍后处理的关系。
消息与事件驱动为此提供另一种组织方式:把可延后的工作交给后续处理方,调用方不必始终保持当前请求等待。消息可以表达待执行的工作,事件可以表达已经发生的事实。具体语义取决于系统约定,不能只看到有消息队列,就认定业务步骤已经正确解耦。尤其要明确哪些结果能立即对用户承诺。
“订单已创建”应以可靠的订单状态为依据,不能仅因某个代理接收了网络请求就发布。订单写入与事件发布之间还可能出现部分成功:数据库写入成功而消息未发出,或者消息重发导致消费者重复处理。这些问题需要数据与消息的协同机制,不能依赖增加 HTTP 重试来解决,C38 会继续展开基础处理思路。
异步也不意味着失败消失。调用链里的等待变成了消息积压、处理延迟、重复投递和结果查询等新问题。用户接口可能先返回受理状态,再通过查询获取最终结果;界面文字和业务承诺必须匹配真实状态。需要立即确认库存预占成功的操作,也不能只为缩短响应就把失败风险隐藏到后台。
进入下一课前,可以拿订单流程逐项判断:哪些步骤必须立即给出结果,哪些只需要最终完成,哪些具有不可随意重复的副作用。同步链路保留清晰的入口、身份和时间预算;适合延后的步骤再考虑消息边界。服务网格、API 网关与消息系统可以共存,各自解决通信路径上的不同问题,选择应来自业务等待关系与可靠性要求。
放进方案里
为订单 API 补写 1 页流量连通说明,先画客户端、入口网关、订单服务、库存服务和数据库,在线上标注协议与方向。把外部入口和内部调用的边界框出来,再写清域名、路径与目标服务的对应关系。若数据库连接没有使用网格提供的请求级能力,应在图旁注明,避免读图者把代理能力扩展到所有协议。
用简短表格分别填写用户身份、工作负载身份和数据库账号由谁验证,以及业务授权由谁执行。给订单查询与订单提交分别注明副作用:查询失败是否可以再试,提交超时后怎样查询真实结果,幂等键如何沿同 1 次业务操作保持稳定。不要把“已部署网关”当成这些问题的答案。
接着按用户可接受的总等待时间分配预算,写出入口、订单处理和库存调用的限制与计时范围,并为响应返回留出余量。列出客户端、入口、应用客户端库和内部代理的尝试上限,指定主要重试责任,再计算最坏情况下库存可能收到多少次尝试。数值需要结合实际延迟验证,不能直接把课文示例当成生产默认值。
补充 3 类验收:正常调用能返回,身份不符合要求时在预期位置拒绝,依赖延迟超过预算时由预期组件生成错误。每类保留请求标识、入口耗时、错误来源和实际尝试数量。另记客户端结束等待后内部任务是否继续,以及未及时取消的任务由谁跟进结果,特别关注数据库已提交而响应丢失的情况。
最后写明规则负责人、发布范围与恢复方式,说明如何确认配置已到达执行流量的实例。若服务数量和通信策略尚少,可以先选择较轻的方案,并记录未来何种问题会触发网格评估。方案交付的判断标准是同事能够据此部署、验收和排查,而不是列出多少组件名称。
10 小结:下一步消息与事件驱动(C38)
API 网关管理入口,服务网格管理内部服务通信;边界、身份与策略责任需要明确标注。超时限制等待,重试增加尝试,熔断影响后续调用,配置时必须放进同 1 个时间与负载预算里考虑。
自测时试着解释:为什么外部出现 504 不能直接判定是网关超时?为什么 2 层各最多尝试 3 次可能变成 9 次库存调用?为什么客户端超时不能证明订单没有创建?如果能用路径、时间线和业务状态回答,就已经建立了本课需要的基本判断方法。
下一课 C38 转向消息与事件驱动,继续讨论哪些工作可以延后、如何理解重复处理,以及如何让用户看到与真实业务状态一致的结果。