4. API网关与路由:Kong、Spring Cloud Gateway、Nginx+Lua,统一入口与流量管控

聊到微服务架构,API网关几乎是绕不开的组件。我见过不少团队,一开始觉得「服务不多,直接调呗」,结果半年后接口管理乱成一锅粥。说白了,网关就是整个系统的「前厅接待」,所有外部请求都得先过它这一关。

今天咱们就掰扯掰扯,市面上主流的三种网关方案:Kong、Spring Cloud Gateway,还有经典的Nginx+Lua。每种方案我都踩过坑,也总结了一些实战经验。

4.1 为什么需要API网关?

先问个问题:没有网关行不行?当然行,小项目完全OK。但一旦服务数量超过10个,你想想看——

  • 每个客户端都要知道所有服务的地址?
  • 认证鉴权逻辑在每个服务里重复写?
  • 限流、熔断、日志,每个服务自己搞一套?

嗯,这就是典型的「分布式泥潭」。网关的作用就是把这些横切关注点统一收口。我个人习惯把网关的职责归纳为三点:

  1. 路由转发:根据请求路径、Header、参数,分发到对应后端服务
  2. 横切能力:认证、限流、日志、监控,在网关层统一处理
  3. 流量管控:灰度发布、蓝绿部署、熔断降级,网关是理想的流量控制点

核心原则:网关只做「路由+横切」,不要塞业务逻辑进去。我在项目中见过有人把订单校验写在网关里,结果每次改需求都要重启网关集群——这其实是个坑。

4.2 Kong:基于OpenResty的企业级网关

Kong是我最早接触的网关产品。它基于OpenResty(Nginx+Lua),但封装得很完善,提供了管理API和插件机制。

4.2.1 核心架构

Kong的架构其实不复杂:

  • Kong Server:基于OpenResty的代理层,处理实际请求
  • 数据库(PostgreSQL/Cassandra):存储路由、服务、插件配置
  • 管理API:RESTful接口,动态增删路由和插件

我记得第一次用Kong时,最惊艳的是它的插件生态。认证、限流、日志、转换,官方提供了上百个插件,而且支持Lua自定义插件。

4.2.2 路由配置示例

通过管理API创建一个路由,把 /api/users/* 转发到用户服务:

# 添加服务
curl -i -X POST http://localhost:8001/services \
  --data 'name=user-service' \
  --data 'url=http://user-svc:8080'

# 添加路由
curl -i -X POST http://localhost:8001/services/user-service/routes \
  --data 'paths[]=/api/users' \
  --data 'strip_path=true'

这里有个细节:strip_path=true 表示去掉前缀再转发。比如请求 /api/users/profile,后端收到的是 /profile。这个设计很实用,避免了后端服务感知网关路径。

4.2.3 避坑指南

我曾经在生产环境遇到过Kong的性能问题。原因是开启了太多全局插件,每个请求都要跑一遍插件链。后来我调整了策略:只在具体路由上挂载需要的插件,性能提升了30%以上。

另外,Kong的数据库是个单点。如果PostgreSQL挂了,虽然已有路由缓存还在,但无法动态更新配置。我建议用Cassandra做高可用部署,或者至少给PostgreSQL做主从。

4.3 Spring Cloud Gateway:Java生态的首选

如果你的技术栈是Spring全家桶,Spring Cloud Gateway(SCG)几乎是顺理成章的选择。它基于Spring WebFlux,完全异步非阻塞,性能不错。

4.3.1 核心概念

SCG的三个核心概念:

  • Route:路由定义,包含ID、目标URI、断言集合、过滤器集合
  • Predicate:断言,匹配请求条件(路径、Header、参数等)
  • Filter:过滤器,对请求/响应做处理

4.3.2 配置示例

用YAML配置一个路由,超级直观:

spring:
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20

你看,限流、路径重写都在配置里搞定。我个人习惯把网关配置放在配置中心(比如Nacos),这样改路由不用重启。

4.3.3 实战经验

SCG有个坑:它基于WebFlux,所以很多Servlet相关的Filter(比如Spring Security的某些Filter)不能直接用。我刚开始迁移时,把老项目的Filter直接搬过来,结果启动报错。

小技巧:SCG的过滤器分两种——GlobalFilter和GatewayFilter。GlobalFilter作用于所有路由,GatewayFilter只作用于特定路由。我建议优先用GatewayFilter,粒度更细,也更容易排查问题。

4.4 Nginx+Lua:灵活到极致

说到网关,怎么能不提Nginx?它本身就是优秀的反向代理。加上Lua脚本,几乎可以做到任何你想要的逻辑。

4.4.1 为什么还要Nginx+Lua?

Kong和SCG虽然功能丰富,但有些场景它们反而「太重」了。比如:

  • 只需要简单的路由+限流,不想引入数据库
  • 有非常定制化的需求,插件满足不了
  • 团队对Nginx很熟悉,不想学新框架

这时候,Nginx+Lua就是最佳选择。你想想看,Nginx处理静态请求和反向代理的能力,加上Lua的动态逻辑,简直是「老司机开手动挡」——灵活但需要技术功底。

4.4.2 一个简单的路由示例

用OpenResty(Nginx+Lua的发行版)实现动态路由:

location /api/ {
    access_by_lua_block {
        local path = ngx.var.uri
        if string.match(path, "^/api/users") then
            ngx.var.upstream = "user_cluster"
        elseif string.match(path, "^/api/orders") then
            ngx.var.upstream = "order_cluster"
        else
            ngx.exit(404)
        end
    }
    proxy_pass http://$upstream;
}

这段代码很简单,但已经实现了基本的路径路由。实际项目中,我一般会把路由规则放在Redis里,Lua定期拉取,实现动态更新。

4.4.3 限流实现

Nginx+Lua做限流,可以用lua-resty-limit-traffic库:

local limit_req = require "resty.limit.req"
local lim, err = limit_req.new("my_limit", 10, 20)
local delay, err = lim:incoming(ngx.var.binary_remote_addr, true)
if not delay then
    ngx.exit(429)  -- 返回429 Too Many Requests
end

嗯,这里要注意:限流状态默认存在共享内存里,如果Nginx多worker进程,需要确保共享内存大小足够。我曾经因为没配好共享内存,导致限流失效,线上被刷了一波流量。

避坑:Nginx+Lua虽然灵活,但Lua代码的质量直接影响网关性能。我见过有人把复杂的业务逻辑写在Lua里,结果CPU飙升。记住:网关层只做轻量级处理,重逻辑交给后端服务。

4.5 三种方案对比

说了这么多,到底选哪个?我整理了一个对比表:

维度 Kong Spring Cloud Gateway Nginx+Lua
技术栈 OpenResty + Lua Java + WebFlux Nginx + Lua
配置方式 管理API / 声明式 YAML / Java DSL 配置文件 + Lua脚本
插件生态 丰富(官方+社区) 中等(Spring生态) 需自行开发
性能 高(C语言核心) 中高(异步非阻塞) 极高(纯C)
学习成本 中等 低(Java团队) 高(需Lua功底)
适用场景 企业级、多团队 Java微服务生态 高定制、高性能

我个人建议:

  • 如果团队Java为主,选Spring Cloud Gateway,无缝集成
  • 如果需要丰富的开箱即用功能,选Kong
  • 如果追求极致性能或高度定制,选Nginx+Lua

4.6 流量管控实战:灰度发布

最后聊一个网关的典型场景——灰度发布。说白了,就是让一部分用户先体验新版本,验证没问题再全量上线。

用Kong实现灰度,可以通过自定义插件,根据Header或Cookie分流:

-- 灰度插件伪代码
local version = ngx.var.http_x_version
if version == "v2" then
    -- 路由到新版本服务
    ngx.var.upstream = "user-service-v2"
else
    ngx.var.upstream = "user-service-v1"
end

用Spring Cloud Gateway,可以通过权重路由实现:

spring:
  cloud:
    gateway:
      routes:
        - id: user-v1
          uri: lb://user-service-v1
          predicates:
            - Path=/api/users/**
            - Weight=user-group, 90
        - id: user-v2
          uri: lb://user-service-v2
          predicates:
            - Path=/api/users/**
            - Weight=user-group, 10

你看,10%的流量走v2,90%走v1。这个配置在灰度期间可以动态调整,直到100%切到v2。

经验之谈:灰度发布一定要配合监控。我习惯在网关层加一个自定义Header(比如X-Gray-Tag),后端服务打印到日志里,方便追踪每个请求走了哪个版本。出问题时,能快速定位是灰度版本的问题还是基础服务的问题。

好了,关于API网关和路由,今天就聊到这儿。网关是整个微服务架构的「守门员」,选对方案、用好它,能省掉很多运维的麻烦。下一章咱们聊聊服务网格,那又是另一个有意思的话题了。