所有外部流量汇聚到一个入口,天然适合做统一处理。问题是”统一处理”很容易失控:路由、鉴权、限流、日志、协议转换、数据聚合、业务校验、流程编排越堆越多,网关从基础设施变成最复杂的单点,改一行配置要全公司排期。划清职责边界,比挑选具体的网关实现更重要。
网关该做与不该做
判断标准可以简化为两条:是否与传输协议相关;是否需要对所有服务一致执行。满足任一条倾向放网关,都不满足就该放回业务服务。
适合放在网关的事:
路由。把路径或域名映射到后端服务,这是网关本职。包含版本路由(/v1、/v2 共存)、灰度路由(按 Header 分流到不同版本)、路径重写(对外路径与内部路径解耦)。
TLS 终止。证书集中管理与到期告警,内部链路走明文或网格层加密,避免每个服务各自处理证书轮换。
鉴权。校验调用者身份、验签、校验 token 有效期,决定是否放行。
限流。入口侧总量保护,按来源、API key、租户设配额。
日志与埋点。访问日志、请求 ID 生成、调用量统计。放在网关能保证格式统一,也不会被某个服务遗漏。
不适合放在网关的事:
业务编排。一个请求要依次调用三个服务并合并结果,这是业务流程。写在网关里等于把业务代码搬到一个难以单元测试、无法独立发布的地方。
过度的数据聚合。为省一次客户端请求而在网关里拼装七八个下游响应,客户端每次需求微调都要改网关,且聚合的失败路径(某个下游超时怎么办)极难处理。
业务参数校验。”这个商品能否购买”是领域规则,网关不具备判断所需的上下文。网关只做结构校验:类型、必填、长度上限。
区分的实操方法:如果一项逻辑需要查询业务数据库才能做判断,它就不属于网关。
网关与 BFF 的分工
客户端多样化的现实是:移动端想要粗粒度的聚合响应,桌面端想要细粒度字段,第三方开放 API 又要求稳定契约和严格配额。让它们共用一个接口集,结果往往是移动端为了三个字段多拉五次接口。
BFF(Backend for Frontend)为每类客户端单独提供一层后端适配:
移动端 ──> Mobile BFF ──┐
桌面端 ──> Web BFF ──┼──> API 网关 ──> 各业务服务
第三方 ──> Open BFF ──┘
分工大致是:网关处理与传输、安全、流量相关的横切关注点,且对所有后端一致生效;BFF 处理与特定客户端体验相关的适配,允许按客户端独立演进。
两者容易混淆的原因是很多团队把 BFF 的能力直接塞进了网关。区别在于演化速度:网关的变更应该低频且需要审批,BFF 的变更跟着客户端迭代走,一周可以发好几次。把高频变更的东西放进低频变更的组件,就会形成流程阻塞。
判断是否需要 BFF 的信号也很直接:同一个页面,移动端和桌面端各自调用了多少次接口、拿了多大比例的冗余字段。如果这个数很高,BFF 值得引入;只有单一客户端时,BFF 就是多余的一层。
鉴权放在网关的得与失
在网关统一鉴权省掉了每个服务重复实现校验逻辑,也不会出现某个服务忘记校验 token 的情况,吊销一处生效全站。代价是授权模型被压扁了。
下游失去细粒度判断能力。网关知道”调用者是谁”,难以判断”他能否访问这条具体记录”。例如”用户只能看自己部门的订单”,部门归属是业务数据,网关不具备判断条件。
常见做法是拆成两段:
网关:认证 Authentication —— 你是谁,token 是否合法、是否过期、是否在黑名单
下游:授权 Authorization —— 你能不能操作这条具体资源
网关校验通过后,把用户标识以可信方式传给下游。关键是防伪造:必须由网关覆写这些头部,下游只信任来自网关的那一层(内部走 mTLS 或在 sidecar 层签名),不能在普通 Header 上传递可伪造的身份。
# 网关覆写内部标识头部,覆盖客户端可能伪造的同名 Header
location /api/ {
proxy_set_header X-User-Id $verified_user_id;
proxy_set_header X-User-Scopes $verified_scopes;
proxy_set_header X-Request-Id $request_id;
proxy_pass http://upstream;
}
配套规则:反向代理前先清除客户端传入的同名头部,否则外部请求可以直接伪造身份。
权限模型的变更成本随之上升。网关覆盖不了的规则仍要在下游实现,最终是”网关粗鉴权 + 下游细鉴权”的双层结构。这不是缺陷,但必须写进设计规范,否则团队会对”到底在哪校验”产生分歧,导致两边都做一半。
单点风险与多活
网关挂等于全站挂,这是集中的必然代价。缓解方向有四个:
无状态化。网关不存会话、不存路由以外的状态,实例可随意扩缩,状态放配置中心或分布式存储。
水平扩容 + 负载均衡。网关前面再挂一层四层负载均衡,实例跨可用区部署。单实例故障不能被自动重启掩盖,要能看到并报警。
按业务拆分网关。流量规模和 SLA 差异明显的场景(例如内部管理与对外开放 API)拆成两个独立集群,避免对外流量冲击内部管理链路。代价是运维两套配置。
故障时的降级通道。网关整体不可用时是否有备用直连路径用于紧急止损,这条路径平时要演练,否则真出故障没人记得怎么用。
配置中心的依赖也要考虑:如果网关的路由必须实时从配置中心拉取,那么配置中心不可用就等于网关不可用。稳妥做法是本地保留上次成功加载的配置快照,启动时即使配置中心不可用也能加载。
多一跳的延迟成本
每个请求多经过一个组件,就多一次连接建立、一次协议解析、一次序列化。这一跳通常是亚毫秒到几毫秒的量级,但对 P99 影响明显,尤其是本身就很快的接口。
控制成本的几个做法:上游保持长连接避免每次握手;网关与同机房后端通信避免跨可用区;请求体不做不必要的全量解析(只取头部做路由判断时就不解析 body);压缩与解压的位置要算清楚——面向公网的入口保留压缩,内部链路通常省略。
对延迟极度敏感的链路(例如行情推送、实时竞价)不适合走常规网关,可以考虑专用通道。
配置热更新的实现要点
网关的路由和限流规则变更如果不支持热更新,就意味着每次调整都要重启,而重启网关是不可接受的。
要点有三个:
版本化。每次配置变更生成版本号,网关记录当前生效版本。出问题时按版本号回滚比按内容回滚可靠得多。
原子性与校验。配置不可部分生效。加载前先做语法预校验和依赖校验(例如引用的上游服务是否存在),校验失败就整体拒绝,保持旧配置继续运行。典型陷阱是推了一半的配置导致路由表不一致。
灰度发布。先推给少量网关实例,观察错误率和延迟指标,再全量。配置错误的影响面往往比代码错误更大,因为它是瞬间全站生效的。
# 一份带版本与校验元信息的网关路由片段
version: 2026.10.21-003
routes:
- id: order-detail
match:
path: /api/v2/orders/{id}
methods: [GET]
upstream: order-service
timeout: 800ms
retry:
attempts: 2
perTryTimeout: 300ms
rateLimit:
key: ${header.x-api-key}
quota: 200
window: 60s
热更新配套的观测也不可少:配置版本号本身应该是一个指标标签,出现问题时能立刻把错误率上升与某次配置变更关联起来。没有这层关联,配置引发的故障会伪装成随机的线上抖动。
评论(2)
标题有点劝退,点进来发现是真的干货。
我们用的是另一个版本,行为不太一样,但思路可以借。
请 登录 后参与评论。