主题
API 网关与服务网格:系统设计里的流量入口层怎么画
本文是系统设计系列 L2 组件积木篇。前置:29. 核心组件手册:LB、缓存、队列与 CDN。 学完可以配合面试题食用:30. 数据层设计、19-multi-region-active-active-system
网关在系统设计图里到底画在哪
不少面试者在白板上画架构图时,网关的定位很模糊——画在负载均衡后面、画在某个微服务前面、或者干脆不画。实际上,网关是流量进入业务系统的第一道关口,它夹在 LB 与业务服务之间,职责清晰但边界容易混淆。
先看一张标准分层:
DNS → LB(四层) → API 网关(七层) → BFF(可选) → 业务微服务- LB(负载均衡):四层,按 IP+端口分发,不关心请求内容。单机吞吐 10M+ pps,但做不了路由判断。
- API 网关:七层,解析 HTTP 请求头、路径、Cookie,做路由转发、鉴权、限流、协议转换。吞吐低于 LB,但灵活度远高。
- BFF(Backend For Frontend):如果客户端有多种(Web/iOS/Android/小程序),BFF 为每种客户端做一次请求裁剪和聚合。BFF 是网关的下游,不是替代品。
网关不该做的事:执行业务逻辑、访问数据库、做重计算。网关是"管道",不是"业务处理器"。如果一个网关里塞了太多业务判断,它就退化成了"单体应用的 Servlet 转发器"。
网关该做什么,不该做什么
职责清单
路由与反向代理:按 path/host/header 把请求分到对应后端服务。这是最基础的功能。
鉴权与身份认证:统一拦截未登录请求,鉴权下发 Token。网关层做一次粗鉴权(Token 是否有效、用户是否封禁),细粒度权限留给业务服务。
限流与熔断:按 API、用户、IP 做限流。网关层的限流是第一道防线,挡住突发的恶意流量(比如爬虫),业务层的限流做精细控制。
灰度发布与流量染色:按 Header/Cookie 把流量切到新版本服务。网关层是灰度路由的天然锚点:不需要改 DNS 或 LB 配置,网关层判断条件即可。
协议转换与请求/响应改写:外部调用是 HTTP/JSON,内部可能是 gRPC/Protobuf,网关做转换。也可以统一加响应头、改写请求路径。
统一日志与监控:网关层能看到所有请求的入口流量,在这里统一采集请求耗时、错误码分布、慢接口追踪,比每个服务各自打日志更完整。
不该做的事
- 不执行业务逻辑:网关里写 if-else 判断业务分支,意味着每次业务变更都要改网关,网关成了"胖代理"。
- 不直接访问数据库:网关层做数据查询意味着它跟业务耦合,也失去了通用性。
- 不做重计算:大数据量聚合、图片处理、文件转换不在网关做,走异步任务或下沉到专用服务。
- 不做长连接管理:WebSocket 有状态,网关层处理长连接会耗尽连接池,通常直接穿透到业务服务,或由独立网关组件处理。
网关选型:从 Nginx 到 APISIX 的演进
Nginx / OpenResty
Nginx 做网关是最常见的方案。轻量、稳定、单机几万 QPS 没问题。配合 lua-nginx-module(OpenResty)可以写 Lua 脚本做鉴权、限流、灰度。
nginx
# 网关限流 + 灰度路由示例
http {
# 限流区域:每个 IP 每秒 10 个请求
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# 灰度路由 upstream
upstream stable_backend {
server 10.0.1.1:8080 weight=9;
}
upstream canary_backend {
server 10.0.2.1:8080 weight=1;
}
server {
listen 80;
location /api/ {
# 限流
limit_req zone=api_limit burst=20 nodelay;
# 灰度判断:Header 带 canary=true 走灰度
set $backend "stable_backend";
if ($http_x_canary = "true") {
set $backend "canary_backend";
}
proxy_pass http://$backend;
proxy_set_header X-Request-ID $request_id;
}
location /auth/ {
# 鉴权转发到 auth 服务
auth_request /_internal/validate_token;
proxy_pass http://auth_service;
}
# 内部鉴权接口
location = /_internal/validate_token {
internal;
proxy_pass http://auth_service/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
}
}Nginx 做网关的局限:动态路由变更需要 reload,Lua 脚本维护成本高,多租户隔离弱。适合中小规模团队。
Spring Cloud Gateway
Java 技术栈的标配。基于 WebFlux(Reactor)非阻塞,配置路由通过 Java DSL 或 YAML。天然对接 Nacos/Eureka 做服务发现,整合 Sentinel 做限流降级。
yaml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"Spring Cloud Gateway 适合 Java 全家桶团队,但依赖 Spring 生态,gateway 本身的性能瓶颈在 Java 线程模型上(即使 WebFlux 非阻塞,Java 的 GC 停顿和内存占用还是比 Nginx 高一个量级)。
Kong / APISIX
基于 OpenResty 的云原生网关,核心优势是插件化 + 动态配置。Kong 用 Lua 插件,APISIX 已经支持 Lua/Java/Go/Python 多语言插件。通过 admin API 或控制面动态增删路由,不需要 reload。
APISIX 的限流 + 鉴权配置:
yaml
# 通过 Admin API 动态创建路由
curl http://127.0.0.1:9180/apisix/admin/routes/1 -X PUT -d '
{
"uri": "/api/v1/*",
"plugins": {
"limit-count": {
"count": 100,
"time_window": 60,
"rejected_code": 429
},
"key-auth": {
"key": "api-key-xxx"
},
"prometheus": {
"prefer_name": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"10.0.1.1:8080": 1,
"10.0.1.2:8080": 1
}
}
}'Kong/APISIX 适合对动态路由和多租户有要求的场景(API 开放平台、SaaS 平台)。坏处是运维复杂度高——自己维护一个 APISIX 集群,业务量不够大时还不如 Nginx 省心。
云原生网关
云厂商的 API 网关产品(阿里云 API Gateway、AWS API Gateway、腾讯云 API 网关)是托管方案。不需要自己运维,自带鉴权、限流、监控、计费能力。缺点:绑定云厂商,跨云迁移困难,高阶定制受限。
选型口诀:小团队用 Nginx,Java 生态用 SCG,开放平台用 APISIX/Kong,上云用托管。
网关与注册中心、配置中心的协作时序
网关不是独立工作的。一个请求从到达网关到转发到后端,实际要跟注册中心和配置中心走一遍:
mermaid
sequenceDiagram
participant Client as 客户端
participant GW as API 网关
participant Registry as 注册中心(Nacos)
participant Config as 配置中心
participant Service as 后端服务
Note over GW,Registry: 启动阶段
GW->>Registry: 1. 订阅服务列表
Config->>GW: 2. 推送路由规则(灰度/限流配置)
Note over Client,Service: 运行时
Client->>GW: 3. HTTP 请求
GW->>GW: 4. 鉴权(Token 校验)
GW->>GW: 5. 限流检查
GW->>GW: 6. 灰度判断 → 选择版本
GW->>Registry: 7. 获取可用实例列表
GW->>Service: 8. 转发请求(带上灰度标签)
Service->>GW: 9. 响应
GW->>Client: 10. 返回结果
Note over GW,Registry: 故障处理
Service-->>GW: 连续超时/5xx
GW->>GW: 熔断降级 → 摘除实例
GW->>Client: 返回降级响应关键点:网关持有的「服务实例列表」是本地缓存的,不是每次请求都去查注册中心。注册中心通过长连接推送变更,网关收到变更后更新本地缓存。如果注册中心挂了,网关用缓存继续工作,这就是"AP 优于 CP"在网关层的体现。
Service Mesh:网关的下一步演变
网关解决了"入口流量"的问题,但服务之间的调用(东西向流量)还是每个服务自己处理——重试、超时、熔断、mTLS 这些能力,要么每个服务重复实现,要么依赖库的方式引入。Service Mesh 把这些问题从应用层下沉到基础设施层。
Istio + Envoy 是当前最主流的 Service Mesh 方案。每个服务部署一个 sidecar proxy(Envoy),所有进出流量都经过 sidecar。控制面(Istiod)下发配置,数据面(Envoy)执行路由、熔断、重试、观测。
网关 + Sidecar 的分层
外部流量 → 入口网关(Istio Ingress Gateway) → sidecar → 服务 A → sidecar → 服务 B入口网关跟 API 网关的职责类似,但由 Istio 统一管理——路由规则、mTLS 证书、流量切分全部通过 K8s CRD 声明式配置。
yaml
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service
spec:
hosts:
- user-service
http:
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: user-service
subset: v2
weight: 100
- route:
- destination:
host: user-service
subset: v1
weight: 90
- destination:
host: user-service
subset: v2
weight: 10
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: user-service-circuit-breaker
spec:
host: user-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 100
http:
http1MaxPendingRequests: 10
maxRequestsPerConnection: 10
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s这个配置做了三件事:按 Header 分流(灰度)、连接池限制、熔断检测。全部在 Istio 层面完成,应用代码一行都不用改。
Service Mesh 引入后,API 网关的职责怎么变
网关的熔断、重试、mTLS 能力下沉到 sidecar 后,网关可以更"薄":
- 网关负责:外部鉴权、限流、协议转换、路由分发
- Sidecar 负责:服务间熔断、重试、超时、mTLS、分布式追踪
网关不再需要操心"后端服务之间怎么调",只关心"外部请求怎么进入内网"。
要不要上 Service Mesh?看三点:有没有多语言服务(Java/Go/Node 混布,统一 mTLS 和熔断逻辑)、流量治理需求是否频繁变更(CRD 配路由比改代码重启快)、团队有没有 K8s 运维能力(Service Mesh 的运维复杂度是网关的 3-5 倍,小团队慎入)。
什么规模不需要网关
日活 10 万以下、微服务少于 5 个的系统,不需要独立的 API 网关层。
少于 5 个服务时,Nginx 直接做反向代理就够了。路由配置写在 nginx.conf 里,鉴权在业务服务的 filter 里做,所谓的"统一入口"需求其实没那么急迫。
网关是"解决分布式复杂性的工具",不是"分布式系统标配"。在架构图上画网关之前,先问一个问题:不加网关,哪里会痛? 答案越具体,网关越需要加;答案越模糊,说明还不需要。