阶段一 · 微服务架构与通信

API Gateway 与 BFF

一句话总结

API Gateway 是微服务的"门面"--外部流量的唯一入口.
它把路由,认证,限流,协议转换等横切关注点从业务服务中剥离出来,让后端服务只专注于业务逻辑.
BFF(Backend for Frontend)则是 Gateway 的变体,为不同客户端量身定制 API.

为什么需要 API Gateway

假设你有 10 个微服务,客户端(Web / App / 小程序)直接调每个服务:

没有 Gateway 时:
Web App ──→ 用户服务 (auth + /users)
──→ 订单服务 (auth + /orders)
──→ 商品服务 (auth + /products)
──→ 搜索服务 (auth + /search)
──→ 支付服务 (auth + /payments)

问题:
1. 客户端要知道所有服务地址
2. 每个服务都要重复实现 auth / 限流 / CORS
3. 服务拆分/合并时客户端要改
4. 内部服务直接暴露在公网

引入 API Gateway 后:

[客户端
(Web/App)] → [API Gateway]

Gateway 解决的核心问题:

关注点 没有 Gateway 有 Gateway
服务发现 客户端维护多个服务地址 客户端只知道 Gateway 一个入口
认证鉴权 每个服务各自实现 Gateway 统一验证 token,内部通信信任
限流 每个服务各自实现 Gateway 统一限流保护后端
协议转换 客户端必须用服务的协议 对外 REST,内部转 gRPC
API 聚合 客户端发 N 个请求拼数据 Gateway 一次请求聚合多服务数据
安全隔离 内部服务暴露在公网 只有 Gateway 有公网入口

API Gateway = 酒店前台.
住客(客户端)不需要知道每个部门(服务)的内线号码,只需要拨前台,前台负责转接,验证身份,拦截骚扰电话(限流),翻译语言(协议转换).

Gateway 核心能力

路由(Routing)

根据 URL 路径,HTTP 方法,Header 等条件将请求分发到对应的后端服务:

# 以 APISIX 配置为例
routes:
- uri: /api/v1/users/*
upstream:
nodes:
user-service:8080: 1
- uri: /api/v1/orders/*
upstream:
nodes:
order-service:8080: 1
- uri: /api/v1/products/*
methods: [GET]
upstream:
nodes:
product-service:8080: 1

认证与鉴权

Gateway 统一验证外部请求的身份(Authentication)和权限(Authorization):

AuthN 与 AuthZ:先证明你是谁,再决定你能干啥

  • Authentication(AuthN,认证):你是谁?验证身份是否真实(authentic).失败返回 401.
  • Authorization(AuthZ,授权):你能干啥?决定是否有权限访问.失败返回 403.
  • 类比刷工卡进楼:刷卡证明"我是张三"(AuthN)→ 卡只能开 3 楼的门(AuthZ).先 N 后 Z,认证在前,授权在后.
  1. 客户端携带 JWT / OAuth2 token
  2. Gateway 验证 token 有效性(签名,过期时间)
  3. 将解析后的用户信息(user_id,roles)注入到 Header 中转发给后端
  4. 后端服务直接信任 Gateway 转发的 Header,无需重复验证
// Gateway 伪代码:JWT 验证 + 注入用户信息
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
claims, err := verifyJWT(token)
if err != nil {
http.Error(w, "unauthorized", 401)
return
}
// 注入已验证的用户信息到内部 header
r.Header.Set("X-User-ID", claims.UserID)
r.Header.Set("X-User-Roles", strings.Join(claims.Roles, ","))
next.ServeHTTP(w, r)
})
}

内部服务不要信任外部 Header

如果后端服务直接暴露给外部(绕过 Gateway),攻击者可以伪造 X-User-ID Header.

确保内部服务只接受来自 Gateway 的流量(通过网络策略 / mTLS / 内部 token).

限流(Rate Limiting)

防止单个客户端或突发流量打垮后端:

算法 特点 适用场景
固定窗口 每 N 秒允许 M 个请求 简单限流
滑动窗口 平滑计数,无窗口边界突刺 精确限流
令牌桶 允许一定突发,长期保持平均速率 允许短时突发(如 API 公共接口)
漏桶 严格平滑输出速率 对下游保护要求高

限流维度:

  • 按 IP(防爬虫)
  • 按 User ID(按用户配额)
  • 按 API Key(按租户/套餐计费)
  • 按路由(热点接口单独限流)

协议转换

对外暴露 RESTful JSON,内部转发为 gRPC:

[客户端] → [API Gateway] → [订单服务]

实现方式:

  • gRPC-Gateway:从 .proto 文件自动生成 REST 反向代理(上一篇讲过)
  • Envoy gRPC-JSON transcoding:配置级转换,无需代码
  • 手写 BFF 层:复杂聚合场景下的定制方案

请求聚合

移动端一个页面可能需要 5 个服务的数据.没有聚合时要发 5 个请求(高延迟,高流量消耗).Gateway 可以合并为一次请求:

// Gateway 聚合层:首页数据
func homepageHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID := r.Header.Get("X-User-ID")

// 并行请求多个服务
var (
user *UserInfo
orders []*Order
recomms []*Product
)
g, ctx := errgroup.WithContext(ctx)

g.Go(func() error {
var err error
user, err = userClient.GetProfile(ctx, userID)
return err
})
g.Go(func() error {
var err error
orders, err = orderClient.ListRecent(ctx, userID, 5)
return err
})
g.Go(func() error {
var err error
recomms, err = recommendClient.GetForUser(ctx, userID, 10)
return err
})

if err := g.Wait(); err != nil {
// 部分失败时降级返回(不影响整体)
log.Warn("partial failure in aggregation", "err", err)
}

json.NewEncoder(w).Encode(HomepageResponse{
User: user,
Orders: orders,
Recommends: recomms,
})
}

BFF 模式(Backend for Frontend)

不同客户端的需求差异很大:

  • Web:大屏幕,可以展示详细信息,带宽不敏感
  • iOS/Android:小屏幕,需要精简数据,带宽敏感
  • 小程序:功能子集,API 更简单
  • 第三方开放 API:标准 REST,版本稳定

用一个 Gateway 服务所有客户端会导致:接口越来越臃肿(为了兼容所有端),改动互相影响,无法按端独立发布.

BFF 架构

┌──────────┐
│ Web 前端 │
└──────────┘


┌─────────────┐
│ iOS/Android │
└─────────────┘


┌──────────┐
│ 小程序 │
└──────────┘


┌──────────┐
│ Web BFF │
└──────────┘


┌────────────┐
│ Mobile BFF │
└────────────┘


┌──────────┐
│ Mini BFF │
└──────────┘


┌──────────┐
│ 用户服务 │
└──────────┘
┌──────────┐
│ 订单服务 │
└──────────┘

每个 BFF 的职责:

  • 为特定客户端裁剪和聚合 后端数据
  • 做协议转换(REST → gRPC)
  • 由对应的前端团队拥有和维护
  • 可以独立部署和演进

BFF 的所有权

BFF 应该由前端/客户端团队拥有,而非后端团队.

因为 BFF 的变更动机来自 UI 需求--"首页要加个推荐位"不应该让后端团队改代码.这也是 BFF 能加速交付的核心原因.

BFF vs 通用 Gateway

通用 API Gateway BFF
职责 路由,认证,限流(横切关注点) 数据聚合,裁剪,适配(业务逻辑)
数量 通常 1 个(或按域分几个) 每种客户端一个
包含业务逻辑 不应该 轻量业务逻辑(聚合,转换)
维护者 平台团队 对应客户端团队

实践中两者常共存:Gateway(横切)→ BFF(聚合)→ 后端微服务.

主流 Gateway 方案对比

方案 语言/生态 特点 适用场景
Kong Lua + Nginx 插件丰富,社区大,企业版付费 通用 API 管理
APISIX Lua + Nginx 国产开源,高性能,etcd 配置中心 国内团队首选
Envoy C++ 云原生标配,xDS 动态配置,Istio 数据面 Service Mesh,K8s 原生
Traefik Go 自动服务发现(K8s/Docker),配置简单 中小团队,K8s 原生
自研 BFF Go/Node.js 完全可控,聚合逻辑灵活 复杂聚合需求

选型建议

K8s 环境:Ingress Controller(Nginx/Traefik)处理基础路由 + APISIX/Kong 处理 API 管理(认证/限流/监控),或直接用 Istio Gateway.
需要 BFF:用 Go 自研轻量 BFF 服务(可以用 connect-go 或 gin),聚合后端 gRPC 服务.
不要把 Gateway 和 BFF 混为一谈--Gateway 是基础设施,BFF 是业务层.

Gateway 反模式

反模式一:胖 Gateway

把业务逻辑塞进 Gateway:数据校验,业务规则,数据库查询...Gateway 变成了新的单体.

正确做法:Gateway 只做横切关注点(路由/认证/限流/监控).需要业务逻辑的聚合放 BFF 层.

反模式二:单点 Gateway

Gateway 是所有流量的入口--它挂了整个系统都不可用.

正确做法:

  • 多实例部署 + 负载均衡(至少 2 个副本)
  • 无状态设计(状态放 Redis / 外部存储)
  • 健康检查 + 自动故障转移
  • 限流保护 Gateway 自身不被打垮

反模式三:Gateway 感知服务内部

Gateway 路由配置与后端服务的内部路径强绑定.服务重构就要改 Gateway 配置.

正确做法:Gateway 按域路由到服务根路径,服务内部路由由服务自己管理.

Gateway 安全实践

  • TLS 终止:Gateway 处理 HTTPS,内部通信可以用 mTLS 或明文(配合网络策略隔离)
  • CORS 处理:在 Gateway 统一配置跨域策略,后端服务不需要关心
  • 请求大小限制:防止大 payload 攻击(body size limit)
  • 请求头清洗:删除客户端注入的内部 Header(如 X-User-ID),防止伪造
  • API 版本管理:通过路径前缀(/v1/,/v2/)或 Header 实现版本路由

Gateway 可观测性

Gateway 是流量入口,天然是最佳的观测点:

指标 含义 告警阈值参考
请求量 QPS 流量趋势,峰值识别 突增 3 倍触发告警
延迟 P50/P99 用户体感 P99 > 1s 告警
错误率 4xx/5xx 服务健康度 5xx > 1% 告警
限流触发次数 是否需要扩容 持续触发说明容量不足
上游响应时间 定位慢服务 按路由分组看哪个后端慢

Access Log 是排障的第一手资料

Gateway 的 access log 应包含:请求时间,客户端 IP,方法,路径,状态码,响应时间,上游服务,trace_id.

遇到问题时,从 Gateway 日志入手定位"哪个服务,哪个接口,什么时候开始慢/错"远比猜测高效.

与 K8s Ingress 的关系

在 Kubernetes 04-05 篇中讲过 Ingress 和 Gateway API.它们和 API Gateway 的关系:

K8s Ingress / Gateway API API Gateway(Kong/APISIX)
层次 基础设施层(流量入口) 应用层(API 管理)
核心功能 路由 + TLS + 基础限流 认证 + 限流 + 监控 + 转换 + 插件
配置方式 K8s YAML(Ingress/HTTPRoute) 自有配置系统或 K8s CRD
灵活度 中等 高(丰富的插件生态)

常见组合:

  • 简单场景:Nginx Ingress Controller 直接当 Gateway 用(路由 + TLS + 基础限流)
  • 中等场景:Traefik / APISIX 作为 Ingress Controller + API Gateway 一体化
  • 复杂场景:外层 LB → API Gateway(认证/限流/监控)→ 内部 Service Mesh(Istio)

快速回顾

  • API Gateway:外部流量唯一入口,负责路由,认证,限流,协议转换等横切关注点
  • BFF:为特定客户端量身聚合/裁剪数据,由对应前端团队拥有
  • Gateway 不做业务逻辑--业务聚合放 BFF 层
  • 协议转换:对外 REST JSON,内部 gRPC(gRPC-Gateway / Envoy transcoding)
  • 限流算法:令牌桶(允许突发)最通用
  • 多实例无状态部署防止 Gateway 成为单点故障
  • 可观测性:Gateway 是最佳观测点(QPS / 延迟 / 错误率 / 上游时间)

动手练习

  1. 启动 Gateway 配路由:用 Docker 启动 APISIX 或 Traefik,配置两条路由分别指向两个后端服务(可以用 httpbin 做 mock)
  2. 压测验证限流:给其中一条路由配置限流(每秒 10 个请求),用 heyab 压测验证限流生效
  3. 实现 BFF 聚合服务:实现一个简单的 Go BFF 服务:接受一个 GET /homepage 请求,并行调用两个后端 gRPC 服务,聚合后返回 JSON
  4. 配置 JWT 认证:Gateway 验证 token 并注入 X-User-ID,后端服务读取该 Header