API 网关实战:认证、限流、路由、协议转换的统一入口设计
适用读者:正在从单体走向微服务、或者多个后端服务需要统一入口的工程师
技术栈:原理与语言无关,文中涉及 Kong / APISIX / Spring Cloud Gateway / Envoy 对比
阅读时间:约 25 分钟 | 难度:中级 | 收获:网关核心能力全景 + 认证/限流/路由设计决策 + 选型 Checklist
前言:从"每个服务都写一遍鉴权"说起
三年前我做的一个项目,四个后端服务(用户、订单、商品、支付),每个服务都自己实现了一遍 JWT 校验、IP 限流、请求日志。结果:
- JWT 密钥轮换要改四处、发布四次;
- 限流阈值改了订单服务,忘了改支付服务,大促时支付先崩了;
- 路由规则散落在四个服务的配置文件里,排查"这个请求到底转发到了哪个服务"要翻四个仓库;
- 一个新同事写了第五个服务,忘了加限流,上线当天被爬虫打挂。
这些问题的本质是:跨切关注点(cross-cutting concerns)散落在每个服务里。
API 网关的思路很简单:
把所有服务的共性需求收到一个统一入口,业务服务只关心业务逻辑。
之前:
客户端 ─► 用户服务(自己鉴权、自己限流、自己记日志)
客户端 ─► 订单服务(自己鉴权、自己限流、自己记日志)
客户端 ─► 商品服务(自己鉴权、自己限流、自己记日志)
之后:
客户端 ─► API 网关(鉴权 + 限流 + 路由 + 日志 + 熔断)─► 业务服务(只写业务)
这篇文章不打算只列功能清单,而是把网关涉及的每一个核心能力
讲清楚为什么、怎么做、有什么坑。
目录
- 为什么需要 API 网关
- 网关的核心能力全景
- 认证:在网关层统一做,业务服务零信任
- 限流:三层粒度与两种经典算法
- 路由:从简单路径匹配到灰度发布
- 协议转换:gRPC、WebSocket、长连接
- 网关选型:Kong vs APISIX vs Spring Cloud Gateway vs Envoy
- 常见陷阱:网关单点、延迟放大、状态管理
- 上线 Checklist + 高频 FAQ
1. 为什么需要 API 网关
1.1 没有网关的痛
假设你有 N 个后端服务,每个都要处理:
这不是"DRY 不 DRY"的代码洁癖问题。
真正的代价是安全漏洞和运维事故:一个服务漏了限流、忘了关 DEBUG 日志、JWT 校验写错——任何一个都可能成为全站突破口。
1.2 网关的本质
一句话:
网关是反向代理 + 跨切关注点管道。
请求 ──► 网关 ──┬── 认证 ──┬── 限流 ──┬── 路由 ──┬── 转换 ──► 后端服务
│ │ │ │
▼ ▼ ▼ ▼
401/403 429 目标服务 协议适配
网关执行的是
管道模型:请求依次经过认证 → 限流 → 路由 → 转发,每一步都可以拒绝或修改请求。
1.3 什么时候不需要网关
诚实地说,
不是所有系统都需要网关:
- 单体应用(一个后端服务):网关是多余的,加一层多一跳延迟
- 两个后端服务且没有跨服务共性需求:用 Nginx 做反向代理就够了
- 内部服务间调用(服务网格场景):用 Service Mesh(Istio/Linkerd),不需要 API 网关
网关的价值跟"服务数量 × 共性需求"正相关。 三个以上面向用户的服务,网关的收益开始明显。
2. 网关的核心能力全景
一张表把所有能力列出来,后面逐个展开:
这篇文章重点讲认证、限流、路由、协议转换四个最核心、最容易踩坑的能力。
3. 认证:在网关层统一做,业务服务零信任
3.1 为什么要在网关做
两个原因:
- 安全边界:网关是流量的唯一入口,所有请求都经过它。在这里做认证,漏网之鱼为零。
- 业务服务零信任:后端服务不应该信任任何调用者——包括网关。正确做法是:网关认证后,把用户身份以签名 Header 或内部 Token 传递给后端,后端校验这个内部凭证而不是重新认证。
3.2 四种认证方案
3.3 JWT 在网关层的最佳实践
最常见的模式:网关验证 JWT → 提取用户 ID → 注入
X-User-Id Header → 后端直接用。
# Kong 的 jwt 插件配置示例
plugins:
- name: jwt
config:
secret_is_base64: false
claims_to_verify:
- exp
key_claim_name: iss
# 验证成功后把 claims 透传给后端
strip_claim: false # 保留 JWT 里的 claims,方便后端取
关键点:
- 网关只验证签名和过期时间,不做业务授权(那是后端的事)
- 不要把 JWT 原文传给后端:网关验证完后,把有用的信息提取到 Header 里,后端只信任网关注入的 Header
- JWT 的 TTL 要短(15 分钟到 1 小时),长 Token 走 Refresh Token 换票——这个流程最好也在网关层处理
- JWT 黑名单:网关本身不维护(那是 Redis 的事),网关应该调一个轻量接口查吊销状态,或干脆用短 TTL 规避
3.4 常见错误
4. 限流:三层粒度与两种经典算法
4.1 三层粒度
网关限流不是"一个数字搞定",而是三层:
三层叠加:一个用户的支付请求,同时受"全局 10000"、"该用户 1000"、"支付接口 100"三个限制,取最严的那个。
4.2 两种经典算法
令牌桶(Token Bucket):
- 桶里放令牌,以固定速率往桶里加(比如每秒 100 个)
- 每个请求消耗一个令牌,桶空了就拒绝
- 允许突发:如果桶是满的,短时间内可以消耗所有令牌,适合应对突发流量
桶容量 100,每秒加 10 个令牌
请求到达 ──► 桶里有没有令牌?──有──► 放行(消耗 1 个)
│
无
▼
429 Too Many Requests
漏桶(Leaky Bucket):
- 请求进入桶,桶以固定速率出水(处理)
- 桶满了新请求直接溢出(拒绝)
- 强制匀速:无论请求多猛,后端收到的速率恒定
推荐:默认用令牌桶,下游是固定速率的外部 API 时用漏桶。
4.3 限流的位置
最佳实践:网关做用户级+全局限流,业务服务做自己特有的业务限流(比如"同一商品限购 5 件"这种语义网关理解不了)。
4.4 限流的返回
被限流的请求应该收到
429 Too Many Requests,并且带上
Retry-After 头(或
X-RateLimit-Reset),告诉客户端什么时候可以重试。
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1718700000
{
"error": "rate_limit_exceeded",
"message": "Too many requests, retry after 30 seconds"
}
5. 路由:从简单路径匹配到灰度发布
5.1 路由的基本形式
5.2 路径设计的两个流派
前缀流派(
/api/v1/users/123):
- 优点:清晰、RESTful、网关配置简单
- 缺点:路径里带版本号,URL 变了客户端要改
子域名流派(
users.api.example.com/v1/123):
- 优点:服务隔离彻底,域名天然路由
- 缺点:DNS 配置复杂,开发环境调试麻烦
我的建议:用前缀流派。 子域名流派在"服务数超过 50"时 DNS 管理会变成灾难,而前缀流派加一个路径重写规则就能解决。
5.3 灰度发布(金丝雀发布)
网关是灰度发布的天然执行者——所有流量都经过它,在这里切流最方便。
按权重切流:
# APISIX 路由配置示例
routes:
- uri: /api/orders/*
upstream:
nodes:
- host: orders-v1.internal
port: 8001
weight: 90
- host: orders-v2.internal
port: 8001
weight: 10
按条件切流(更精准):
# 内部员工走新版本,其他人走旧版本
routes:
- uri: /api/orders/*
vars:
- ["http_x_internal_user", "==", "true"]
upstream: orders-v2
- uri: /api/orders/*
upstream: orders-v1
灰度发布的要点:
- 先切 1%,观察 30 分钟(错误率、延迟、业务指标),再逐步放量
- 灰度期间必须可秒级回滚——网关改一条权重配置就行,不用重新发布
- 灰度的维度要选对:按用户 ID 哈希比按随机权重好,保证同一用户始终走同一版本(避免"刷新一下页面就换了个版本"的困惑)
5.4 路由冲突
当多个路由规则匹配同一个请求时,网关需要确定性决策:
- 最长前缀匹配:
/api/users/admin 优先于 /api/users/*
- 顺序匹配:第一个匹配的规则生效(APISIX、Spring Cloud Gateway 的做法)
- 优先级字段:给每个规则标优先级(Kong 的做法)
务必写测试验证路由顺序。 路由冲突导致的"该走 v1 的流量走了 v2"是最难排查的 bug 之一——因为它在监控里没有任何异常,只有业务逻辑不对。
6. 协议转换:gRPC、WebSocket、长连接
6.1 HTTP ↔ gRPC
后端服务越来越多用 gRPC(强类型、高性能、双向流),但客户端(浏览器、移动端)通常只支持 HTTP/JSON。网关的协议转换能力解决的就是这个矛盾:
浏览器 ──HTTP/JSON──► 网关 ──gRPC/Protobuf──► 后端服务
这不只是"翻译"——还有几个关键差异要处理:
生产建议:别在网关里做 JSON↔Protobuf 的手工转换(维护成本极高)。用 Envoy 的 gRPC-JSON Transcoder 或 APISIX 的 grpc-transcode 插件,它们根据
.proto 文件自动生成映射。
6.2 WebSocket
WebSocket 和网关是天生的"不对付"——网关设计时假设有请求-响应模型,而 WebSocket 是一个长连接。
问题:
- 网关的连接超时(Nginx 默认 60s)会切断空闲的 WebSocket 连接
- 网关的负载均衡策略(Round-Robin)对长连接不友好——一个新连接来了可能全打到一台机器
- 网关的限流是按请求次数,WebSocket 一个连接发 1000 条消息,限流该不该算?
配置要点:
# Nginx 的 WebSocket 配置
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s; # 不要用默认 60s
proxy_send_timeout 3600s;
}
负载均衡策略:对 WebSocket 用
ip_hash(同一客户端 IP 始终到同一后端),而不是 Round-Robin。这样连接重建时不会飘到别的机器。
6.3 SSE(Server-Sent Events)
SSE 是 WebSocket 的"单向版"——服务器推流给客户端,不需要双向通信。网关对 SSE 的支持通常比 WebSocket 好(因为它仍然是 HTTP 长连接,不需要
Upgrade),但同样要注意
别让网关的缓冲把流式响应变成批量响应:
location /api/events {
proxy_pass http://backend;
proxy_buffering off; # 关缓冲,逐行转发
proxy_cache off;
chunked_transfer_encoding on;
}
7. 网关选型:四款主流产品对比
选型建议
- Java 团队、服务数量 < 10:Spring Cloud Gateway,跟 Spring Boot 生态无缝集成,不需要额外部署
- 需要高性能 + 丰富插件:APISIX,国内社区活跃,文档中文友好
- 需要对接多种认证协议(OAuth2/OIDC):Kong,插件生态最成熟
- 大型系统、要配合 Service Mesh:Envoy(作为 Sidecar + 网关双重角色)
- 个人项目或小团队:Nginx + Lua 脚本,或直接用云厂商的网关(阿里云 API 网关、AWS API Gateway)
8. 常见陷阱
8.1 网关变成单点
所有流量走网关 → 网关挂了 → 全站挂。这是最常见的质疑,也是真实风险。
解法:
- 网关至少部署两个实例,前面加负载均衡(L4 或 DNS 轮询)
- 网关本身无状态(限流计数放 Redis,认证缓存放 Redis),实例挂了秒切换
- 网关的健康检查必须配,LB 自动摘除不健康的实例
- 极端情况:网关全挂了,能不能让客户端直连后端?(不能——因为后端没有网关的认证/限流能力。这是网关模式的安全代价,要接受)
8.2 网关引入的延迟
每多一跳就多延迟。网关的处理通常消耗 1-5ms(Lua/C++ 实现),但以下情况会显著放大:
- 网关做了 JWT 验证 + 数据库查询用户信息(额外的 IO)
- 网关做 JSON↔Protobuf 序列化(大 Body 时耗时)
- 网关和业务服务不在同一可用区(跨区网络延迟 1-5ms)
建议:网关和业务服务部署在同一个 VPC/可用区内,网络开销控制在 1ms 以内。
8.3 网关状态管理
网关应该是无状态的,但有些功能天然需要状态:
网关本地内存不应该存任何业务状态。 本地计数在多个网关实例下会不一致,导致限流失效。
8.4 网关的版本管理
网关配置本身就是一种"代码",应该走版本控制:
- Kong/APISIX 的配置导出为 YAML,放 Git
- 变更走 PR + Review(网关配置改错等于全站事故)
- 用声明式工具(Terraform / decK / APISIX Ingress)管理,不要手动改
9. 上线 Checklist + 高频 FAQ
Checklist
- [ ] 网关至少 2 个实例,前面有负载均衡
- [ ] 认证密钥不在网关配置里硬编码,走环境变量或密钥管理服务
- [ ] 限流在 Redis 上实现(非本地计数),多实例共享
- [ ] 限流返回
429 + Retry-After,不是 500
- [ ] WebSocket 连接超时 > 3600s,SSE 关缓冲
- [ ] 路由规则有优先级顺序,不存在歧义
- [ ] 灰度发布有秒级回滚方案(改权重配置)
- [ ] 网关访问日志统一格式,进 ELK/Loki
- [ ] 网关本身有健康检查和监控(CPU、内存、延迟、5xx 率)
- [ ] 网关配置走 Git 版本控制,变更走 PR
- [ ] SSL 证书自动化续期(Let's Encrypt + Cert-Manager)
- [ ] 后端服务不信任客户端传的 Header,只信任网关注入的 Header
FAQ
Q:网关挂了整个系统就挂了,这不算把风险集中了吗?
A:算,所以网关本身要做高可用(多实例 + 健康检查)。但即使网关挂了,也只是一个点挂;而如果 10 个服务各自做认证,任何一个漏了认证都是安全漏洞。
网关是把"10 个分散的安全风险"换成"1 个集中的可用性风险",后者更容易用工程手段(多实例、健康检查、自动恢复)管控。
Q:微服务一定要用 API 网关吗?
A:不一定。服务网格(Service Mesh)是另一种思路——把跨切关注点下沉到每个服务的 Sidecar 代理里,而不是集中到一个网关。网关适合"南北向流量"(外部进内部),Service Mesh 适合"东西向流量"(服务间调用)。中大型系统通常是两者结合:网关管外部入口,Service Mesh 管内部通信。
Q:网关能替代 Nginx 吗?
A:API 网关通常是基于 Nginx/OpenResty 构建的(Kong、APISIX 都是),它们比纯 Nginx 多了插件生态和控制面。如果你只需要静态文件服务 + 反向代理,用 Nginx 就够了;如果你需要认证、限流、灰度发布,用 API 网关。
Q:网关限流和 Redis 分布式限流的区别?
A:网关限流是"入口层限流",在流量进后端之前就挡住,保护效果最好。但网关通常只能按 IP/用户/路径限流,做不到"全局限流"(所有用户共享一个总量)。如果你需要精确的全局限流(比如"整个系统每秒不超过 10000 单"),还得在业务层用 Redis 原子计数做二次限流。
网关限流是第一道防线,不是最后一道。
Q:个人项目有必要上 API 网关吗?
A:大多数个人项目不需要。一个 Django/Express 应用 + Nginx 反向代理就够了。除非你有多个独立的后端服务,或者你想实践一下网关的配置和管理。
结语
API 网关的核心思想可以用一句话概括:
把服务的"怎么进"和"做什么"分开。
业务服务只需要关心"做什么"——处理业务逻辑、操作数据库、返回结果。至于"怎么进"——谁在调用、能不能调用、调得多快、走哪个版本、用什么协议——都交给网关。
回到开头那个项目:上了网关之后,四个服务的认证代码全删了,JWT 轮换改一处就行,限流阈值在 Dashboard 里改一条配置就生效。新同事写第五个服务,自带认证和限流——因为网关已经帮他做了。
但网关也不是银弹。它引入了一个新的单点、新的延迟源、新的配置复杂度。选不选网关、选哪个网关、配哪些功能,取决于你的服务规模和团队能力。
没有网关的系统不一定有问题,但有了网关的系统,跨切关注点一定不会散落。 这一点,就值回票价了。
本文为架构设计经验总结,不涉及具体代码实现。文中 Kong / APISIX / Envoy 配置示例均为示意用途,生产环境请参考官方文档。