服务数量过了 30 个之后,鉴权逻辑散落在各个服务里、限流规则改一处要发五个应用,网关从「可选项」变成了「必需品」。本文记录我们基于 Spring Cloud Gateway 自建网关的过程,重点是那些文档里不会写的权衡。

网关到底该承担什么

立项时最容易犯的错误是把网关做成「万能中间层」。我们划定的职责边界是:路由转发、统一鉴权、流控熔断、协议转换、可观测。业务逻辑(如参数组装、数据聚合)坚决不进网关——网关一旦有了业务语义,就成了新的单体。

核心链路设计

Spring Cloud Gateway 基于 Netty 非阻塞模型,单线程能承载大量并发连接。所有横切能力都收敛到 GlobalFilter 链,按 Ordered 接口控制执行顺序:鉴权 → 限流 → 路由 → 日志。

java
1@Component2public class AuthGlobalFilter implements GlobalFilter, Ordered {34    @Override5    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {6        String token = exchange.getRequest().getHeaders().getFirst("Authorization");7        return tokenService.verify(token)              // 响应式鉴权,不阻塞线程8                .flatMap(user -> {9                    // 用户身份透传给下游:改写请求头10                    ServerHttpRequest mutated = exchange.getRequest().mutate()11                            .header("X-User-Id", user.getId())12                            .build();13                    return chain.filter(exchange.mutate().request(mutated).build());14                })15                .onErrorResume(e -> unauthorized(exchange));16    }1718    @Override19    public int getOrder() {20        return -100; // 最先执行21    }22}

限流:为什么选了 Redis + Lua

网关自带的 RequestRateLimiter 基于 Redis 令牌桶,集群维度统一计数;单机内存限流在多实例下会放过 N 倍流量。我们把限流规则存进配置中心,按「接口 + 用户维度」双键限流,突发流量允许 1.5 倍桶容量应对短时脉冲。

性能优化清单

  • 关闭不必要的过滤器链:默认的 RewritePath 等按路由粒度启用,不做全局挂载。
  • 鉴权结果短缓存:同一 token 30 秒内命中本地 Caffeine,Redis 调用量下降 70%。
  • Netty 参数调优:worker 线程数设为 CPU 核数,连接池 maxLifeTime 小于下游 LB 的空闲超时,避免半开连接。
  • 日志异步化:访问日志写 Disruptor 队列批量落盘,主链路零 I/O。

优化后单实例稳定支撑 2 万 QPS,P99 转发延迟 4ms。最关键的一条经验是:网关上任何同步阻塞调用都是定时炸弹,一次下游慢查询就能把 Netty 事件循环拖垮,所有外部调用必须响应式或隔离线程池。

网关的价值不在「功能多」,而在「稳定地做减法」——它越透明,架构越健康。

扩展性留给未来的三件事

路由规则走配置中心动态下发,新增服务零发布;过滤器按 SPI 机制加载,业务方可以提交自定义过滤器但默认关闭;灰度能力预留在路由谓词层,按用户标签分流到不同集群。这三件事让网关从「项目」变成了可演进的「平台」。