2. API网关与限流:网关架构模式、令牌桶与漏桶算法、分布式限流实现
2.1 API网关架构模式
API网关是微服务架构的流量入口,承担路由、认证、限流、监控等核心职责。当前主流网关包括 Kong(基于 OpenResty/Nginx)和 APISIX(基于 Lua + etcd),两者均采用插件化架构,但设计理念存在差异。
| 对比维度 | Kong | APISIX |
|---|---|---|
| 配置存储 | PostgreSQL / Cassandra | etcd(分布式一致性) |
| 热加载 | DB 轮询 + 缓存刷新 | etcd watch 实时推送 |
| 路由匹配 | 正则 + 优先级 | 前缀树 + 变量优先级 |
| 插件语言 | Lua(自定义需写 C 扩展) | Lua + Wasm + Java/Python 扩展 |
| 性能基准 | ~20K QPS(单核) | ~30K QPS(单核) |
选型建议: 若团队已有 PostgreSQL 运维经验且对实时性要求不高,Kong 生态更成熟;若追求毫秒级配置生效、高并发场景,APISIX 的 etcd 架构优势明显。
2.2 网关请求处理流程
下图展示 API 网关在限流场景下的核心处理链路:
2.3 令牌桶算法与漏桶算法
限流算法的核心是控制请求速率,令牌桶和漏桶是两种最经典的实现方式,两者在突发处理能力上存在本质区别。
| 特性 | 令牌桶(Token Bucket) | 漏桶(Leaky Bucket) |
|---|---|---|
| 核心思想 | 以固定速率生成令牌,请求消耗令牌 | 以固定速率漏出请求,超量排队或丢弃 |
| 突发处理 | 允许短时突发(桶容量决定峰值) | 强制平滑,不允许突发 |
| 适用场景 | 允许突发流量的业务(如秒杀) | 要求严格平滑的场景(如数据库写入) |
| 实现复杂度 | 中等(需维护定时器或时间戳) | 较低(队列 + 固定速率消费) |
| 典型实现 | Guava RateLimiter、Redis-Cell | Nginx limit_req、Kong rate-limiting |
常见误区: 令牌桶的“突发”并非无限制——桶容量决定了最大突发量。若桶容量 = 100,令牌生成速率 = 10/s,则最大突发为 100 个请求,之后速率被限制为 10/s。
2.4 令牌桶算法工作原理
下图展示令牌桶算法的完整生命周期:
2.5 分布式限流实现方案
单机限流无法应对多实例场景,分布式限流需要中心化存储协调状态。以下是三种主流实现:
- Redis + Lua 脚本:利用 Redis 单线程特性,通过 Lua 脚本原子执行令牌桶逻辑,保证并发安全。
- Redis-Cell 模块:Redis 4.0+ 的限流模块,内置 GCRA(通用细胞速率算法),性能优于 Lua 脚本。
- Sentinel 集群流控:阿里开源的限流组件,支持 Token Server 模式,适合 Java 生态。
| 方案 | 延迟 | 一致性 | 运维成本 | 推荐场景 |
|---|---|---|---|---|
| Redis + Lua | ~1ms | 最终一致 | 低 | 中小规模,通用场景 |
| Redis-Cell | <0.5ms | 强一致 | 中(需加载模块) | 高并发,低延迟要求 |
| Sentinel 集群 | ~2ms | 强一致 | 高(需部署 Token Server) | Java 微服务,复杂规则 |
2.6 Redis + Lua 分布式限流实现
以下是一个基于令牌桶算法的 Redis Lua 脚本,支持动态调整速率和容量:
-- 令牌桶限流脚本
-- KEYS[1]: 限流 key
-- ARGV[1]: 令牌生成速率(个/秒)
-- ARGV[2]: 桶容量
-- ARGV[3]: 当前请求消耗令牌数(通常为1)
-- 返回值: 0=拒绝, 1=放行
local key = KEYS[1]
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local requested = tonumber(ARGV[3]) or 1
-- 获取当前桶状态
local token_info = redis.call('HMGET', key, 'tokens', 'last_refresh')
local tokens = tonumber(token_info[1]) or capacity
local last_refresh = tonumber(token_info[2]) or 0
-- 计算时间差,补充令牌
local now = redis.call('TIME')[1]
local elapsed = math.max(0, now - last_refresh)
local new_tokens = math.min(capacity, tokens + elapsed * rate)
-- 判断是否放行
if new_tokens >= requested then
new_tokens = new_tokens - requested
redis.call('HMSET', key, 'tokens', new_tokens, 'last_refresh', now)
redis.call('EXPIRE', key, 10) -- 设置过期时间,避免内存泄漏
return 1
else
-- 令牌不足,更新刷新时间但不扣减
redis.call('HMSET', key, 'tokens', new_tokens, 'last_refresh', now)
return 0
end
性能要点: 上述脚本使用
TIME 命令获取 Redis 服务器时间,避免客户端时间不一致问题。每次调用仅执行 2-3 次 Redis 命令,单实例 QPS 可达 10 万+。建议将限流 key 按客户端 IP 或 API 路径做哈希分片,避免单 key 热点。
2.7 网关限流配置实战
以 APISIX 为例,通过 Admin API 配置基于令牌桶的限流插件:
# 创建路由并绑定限流插件
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
-X PUT -d '
{
"uri": "/api/v1/orders",
"plugins": {
"limit-req": {
"rate": 10, -- 每秒请求速率
"burst": 20, -- 突发容量
"rejected_code": 429, -- 拒绝状态码
"key": "remote_addr" -- 限流维度:客户端IP
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"192.168.1.100:8080": 1
}
}
}'
生产建议: 限流 key 应选择业务标识(如用户 ID、API Key),而非仅依赖 IP。NAT 环境下多个用户共享同一 IP,会导致误限。APISIX 支持
consumer_name、service_id 等更细粒度的 key。
2.8 限流治理最佳实践
- 分层限流:网关层(全局 QPS)→ 服务层(业务限流)→ 资源层(DB 连接池),避免单点瓶颈。
- 动态配置:限流参数应支持运行时调整,APISIX 的 etcd watch 机制可实现秒级生效。
- 监控告警:记录限流触发次数、拒绝率、令牌桶水位,当拒绝率 > 5% 时触发扩容或降级。
- 降级策略:限流后返回友好提示(如 429 + Retry-After 头),避免客户端无限制重试。
- 压测验证:使用 wrk 或 Locust 模拟突发流量,验证限流曲线是否符合预期。
避坑指南: 分布式限流依赖 Redis 可用性,若 Redis 宕机,所有限流规则将失效。建议部署 Redis Sentinel 或 Cluster 集群,并在网关侧配置降级策略(如降级为本地限流,允许一定误差)。