各位同仁,晚上好。今天咱们不聊那些虚头巴脑的概念。聊一聊如何看待API 网关的:它是 Spring Cloud Gateway 或者 Zuul;不全对,可以说他们是整个微服务架构的“看门大爷”兼“前台接待”。

想象一下,如果没有网关,你的前端(App、Web、小程序)就像是一个没有围墙的裸奔广场,谁都能直接冲进你的用户服务、订单服务里撒野。那场面,简直就是灾难片现场。

API 网关的作用是什么?就是给这个广场修个大门。

  • 统一入口:不管你是谁,想进来?先过我这关。
  • 鉴权:查身份证(Token),没带证的保安(Auth Filter)直接轰走。
  • 限流:里面只有 100 个座位,你想挤进来 1000 个人?对不起,后面的 900 个去门口排队(Rate Limiter)。

今天,咱们就以 Spring Cloud Gateway 为主角(毕竟 Zuul 1.x 那个阻塞 IO 的老古董,除了维护老项目,咱们在新架构里就不多提了),带大家看看这个“看门大爷”是怎么在内核层面工作的。

核心原理:它是怎么“拦截”你的?

Spring Cloud Gateway 的底层是 WebFlux + Reactor Netty。这是什么意思?意思是它是异步非阻塞的。就像看一个顶级赛车引擎。你以为它只是拦个车、查个证?错!它是一个在毫秒级甚至微秒级进行流量整形、内存零拷贝、字节码编织的暴力美学机器。

传统的 Servlet 容器(如 Tomcat)像是柜台式银行,来一个人开一个窗口,人多了你就得排队等窗口。而 Gateway 像是麦当劳自助点餐机,一个人负责处理多个请求,效率极高,特别适合高并发场景。

  • Tomcat (旧时代):像银行柜台,来一个人开一个窗口。人多了你就得排队,柜员累死。
  • Netty/SCG (新时代):像是一个只有两个大脑的超级杂技演员(IO 线程池通常等于 CPU 核数)。他同时抛接一万个球(请求)。如果一个球卡住了(比如 IO 等待),他立刻去接下一个,绝不发呆。

它的核心工作流就三个词:HandlerMapping(找路)、GatewayFilterChain(过安检)、RouteLocator(指路牌)。

1. 路由与断言 (Predicate):门卫的检查清单

请求进来了,网关怎么知道把它往哪送?靠的就是 Predicate(断言)。这就像是门卫手里的一张检查清单:

  • “如果是 /api/user/** 开头的,送去用户服务。”
  • “如果是 POST 请求且带有 X-Admin-Token 头的,送去管理后台。”

只要匹配上,就放行。是不是so easy 哪里不会点哪里

2. 过滤器 (Filter):真正的“动手”环节

这是我们要重点讲的黑科技。Gateway 的过滤器分为 GlobalFilter(全局拦截,比如鉴权)和 GatewayFilter(局部拦截,比如特定业务)。

3.分布式限流 (The Shield)

别再用 ConcurrentHashMap 在单机内存里数数了,那是玩具。生产环境必须是Redis + Lua

为什么?因为 Redis 是单线程处理命令的,Lua 脚本能保证操作的原子性。这就像是给所有并发请求发一张“实体票”,而不是口头承诺。

深度代码解剖:Redis Lua 脚本实现令牌桶

我们在网关层嵌入一段 Lua 脚本,让 Redis 替我们抗并发计算。

-- keys[1]: 限流 Key (例如: "rate_limit:/api/order")
-- ARGV[1]: 最大容量 (capacity)
-- ARGV[2]: 当前时间戳 (timestamp)

local key = keys[1]
local capacity = tonumber(ARGV[1])
local now = tonumber(ARGV[2])

-- 1. 获取当前的令牌数量和上次请求时间
local last_time = redis.call('hget', key, 'last_time')
local tokens = redis.call('hget', key, 'tokens')

if not last_time then
    -- 第一次访问,初始化
    redis.call('hset', key, 'last_time', now)
    redis.call('hset', key, 'tokens', capacity - 1)
    return 1 -- 允许通过
end

last_time = tonumber(last_time)
tokens = tonumber(tokens)

-- 2. 计算这段时间内生成的令牌数 (时间差 * 填充速率)
-- 假设每秒填充 capacity 个令牌(简化算法)
local delta_time = now - last_time
local new_tokens = math.floor(delta_time * (capacity / 60)) -- 假设每分钟填满

tokens = math.min(tokens + new_tokens, capacity)

-- 3. 尝试消费令牌
if tokens > 0 then
    tokens = tokens - 1
    redis.call('hset', key, 'tokens', tokens)
    redis.call('hset', key, 'last_time', now)
    return 1 -- 允许通过
else
    return 0 -- 拒绝访问 (限流触发)
end

在 Java 中调用:
使用 ReactiveRedisTemplate 执行这段脚本。注意,必须是Reactive的,不能阻塞 IO 线程。
这就是所谓的“漏桶算法”变种。将复杂的逻辑下沉到 Redis(C 语言编写),利用其单线程特性避免锁竞争,这是高并发架构的黄金法则。

熔断与降级 (The Parachute)

当下游服务挂了,网关不能傻等。我们需要一个断路器。Spring Cloud Gateway 整合了 Resilience4j。但这不仅仅是抛出异常那么简单。真正的深度在于信号量隔离

原理讲解

如果不做隔离,下游服务响应慢会导致网关的 Netty 线程被占满。我们需要为每个下游服务分配一个“舱位”(Semaphore)。舱满了,直接熔断,不再发送请求。

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - name: CircuitBreaker
              args:
                name: myCircuitBreaker
                fallbackUri: forward:/fallback/order-fallback # 降级路径
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10

resilience4j.circuitbreaker:
  configs:
    default:
      slidingWindowSize: 10       # 统计窗口大小
      failureRateThreshold: 50    # 失败率达到 50% 就跳闸
      waitDurationInOpenState: 10s # 跳闸后休息 10 秒再尝试半开

这就像是给你的电路装了个自动跳闸开关。一旦检测到后端“漏电”(超时或报错),立马切断电源,保护前端用户不被卡死,直接送上一份“备用干粮”(Fallback 数据)。

灰度发布与权重路由 (The Chameleon)

怎么做到让 1% 的用户看到新功能?靠的是谓词工厂 (Predicate Factory)

深度代码解剖:加权路由

我们可以利用 Spring 的 @Bean 定义复杂的路由规则。

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("canary_route", r -> r
            // 1. 匹配路径
            .path("/api/new-feature")
            // 2. 加上权重的 Filter
            .and()
            .filter(new WeightGatewayFilterFactory().apply(c -> c.setWeight("canary_group", 10))) // 权重 10
            .uri("lb://new-feature-service"))
        .route("stable_route", r -> r
            .path("/api/new-feature")
            .and()
            .filter(new WeightGatewayFilterFactory().apply(c -> c.setWeight("canary_group", 90))) // 权重 90
            .uri("lb://old-stable-service"))
        .build();
}

这利用了概率论。网关在内存中维护了一个随机数生成器。每次请求过来,掷骰子,1-10 去新服务,11-100 去老服务。这就实现了无感知的流量切分。

API 网关绝不仅仅是一个反向代理。在高级架构师眼里,它是:

  1. 反应式编程的巅峰:利用 Netty 和 Project Reactor,以极少的线程支撑海量并发。
  2. 流量的调度中心:通过 Lua 脚本和 Redis 实现分布式的精准控制。
  3. 系统的防波堤:通过熔断和降级,防止局部故障演变成全局灾难。
  4. 业务的适配器:通过协议转换和 Header 注入,解耦前后端,让业务服务保持纯粹。

下次写网关代码时,请时刻记住:你是在驾驶一架战斗机,不是在骑自行车。小心你的每一行代码,别让它在 IO 线程里阻塞哪怕一毫秒。

实战代码:手写一个“暴躁”的鉴权与限流网关

光说不练假把式。下面这段代码,我会演示如何自定义一个 GlobalFilter,实现两件事:

  1. 统一鉴权:检查 Header 里有没有 Token,没有?401 伺候。
  2. 简单限流:利用 Redis(伪代码演示)做令牌桶,满了?429 拒绝。
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class CustomAccessControlFilter implements GlobalFilter, Ordered {

    // 这里的 redisTemplate 请自行注入,为了演示方便我省略了
    // private RedisTemplate<String, String> redisTemplate; 

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();

        // 1. 【白名单放行】:登录接口、静态资源不需要鉴权
        if (path.contains("/login") || path.contains("/static")) {
            return chain.filter(exchange);
        }

        // 2. 【统一鉴权】:检查 Token
        String token = request.getHeaders().getFirst("Authorization");
        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            // 没带 Token 或者格式不对,直接返回 401
            return unauthorizedResponse(exchange, "哥们,Token 都没带就想闯禁地?");
        }

        // 模拟解析 Token (实际请用 JWTUtil 解析)
        String userId = "user_123"; 

        // 3. 【限流逻辑】(伪代码)
        // 假设我们限制每个用户每秒只能访问 10 次
        /*
        String key = "rate_limit:" + userId;
        Long count = redisTemplate.opsForValue().increment(key);
        if (count == 1) {
             redisTemplate.expire(key, 1, TimeUnit.SECONDS);
        }
        if (count > 10) {
             return tooManyRequestsResponse(exchange, "手速太快了,歇会儿吧!");
        }
        */

        // 4. 【传递用户信息】:把解析出的 UserID 塞进请求头,传给下游微服务
        // 这样下游服务就不用再解析 Token 了,直接从 Header 拿用户 ID,这叫"透传"
        ServerHttpRequest mutatedRequest = request.mutate()
                .header("X-User-Id", userId)
                .build();

        // 5. 【继续执行】:带着新的请求头,冲向下一个过滤器或目标服务
        return chain.filter(exchange.mutate().request(mutatedRequest).build());
    }

    // 定义执行顺序,数字越小优先级越高
    @Override
    public int getOrder() {
        return -100; 
    }

    // 构造 401 响应
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
    
    // 构造 429 响应
    private Mono<Void> tooManyRequestsResponse(ServerWebExchange exchange, String msg) {
        exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
        return exchange.getResponse().setComplete();
    }
}

进阶玩法:限流不仅仅是“数数”

刚才代码里的限流只是最简单的计数器。作为高级架构师,你得知道在生产环境怎么搞。

通常我们会使用 Redis + Lua 脚本 来实现滑动窗口算法或令牌桶算法。Spring Cloud Gateway 官方其实提供了一个 RequestRateLimiter 过滤器,底层就是基于 Redis 的。

spring:
  cloud:
    gateway:
      routes:
        - id: order_service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10  # 令牌桶每秒填充速率
                redis-rate-limiter.burstCapacity: 20  # 令牌桶最大容量

解读

  • replenishRate: 允许用户每秒处理多少个请求。
  • burstCapacity: 允许的突发流量。比如你设置了 20,那么瞬间来了 20 个请求也能扛住,但第 21 个就会被限流。

关于 Zuul 的“墓志铭”

既然提到了 Zuul,我得吐槽两句。
Zuul 1.x 是基于 Servlet 2.5 的,它的模型是同步阻塞的。每一个请求进来,都要占用一个线程。如果请求处理慢,线程池很快就满了,整个网关就卡死了。

所以,除非你还在维护五年前的老代码,否则新项目坚决不要用 Zuul 1.x。
至于 Zuul 2.x?虽然它改成了异步非阻塞,但 Spring 亲儿子 Spring Cloud Gateway 已经是目前的版本答案了!

企业级 API 网关

除了我们刚才聊的统一入口鉴权限流这“老三样”,一个成熟的企业级 API 网关(比如 Kong, APISIX, Spring Cloud Gateway)其实还承担着更多“隐形”但至关重要的职责。

  • API 网关不仅仅是“大门”,它更像是一个集成了安保、物流调度、翻译、急救于一体的综合服务中心。
  • 如果把网关比作一个“超级大厦的前台”,除了查门禁卡(鉴权)和控制人流量(限流),它还得干这些活儿:

1. 协议转换与适配 (Protocol Translation)

这是网关最“老好人”的功能。

  • 场景:你的前端是新的,想调 gRPC;但你的后端老系统还在跑 SOAP 或者 Dubbo。或者移动端为了省流量想用二进制协议,Web 端只能用 HTTP/JSON。
  • 作用:网关在中间充当“翻译官”。客户端发 HTTP/JSON,网关转成 gRPC/Dubbo 传给后端;后端返回二进制,网关再转成 JSON 给客户端。
  • 价值屏蔽底层异构差异。业务服务不用管谁来调我,我只负责干活,格式转换这种脏活累活网关全包了。

2. 灰度发布与流量调度 (Canary Release & Traffic Scheduling)

这是架构师做“手术”时的精密仪器。

  • 场景:你要上线 v2.0 版本,但不敢全量推,怕有 Bug。你想让只有“北京地区”或者“VIP 用户”能看到新版本,其他人还是看旧版本。
  • 作用:网关根据请求头、IP、权重等规则,把流量精准地切分。比如 90% 流量去 v1,10% 流量去 v2;或者满足 X-User-Type: VIP 的请求全部转发到 v2 集群。
  • 价值:实现蓝绿部署、A/B 测试,让发布风险可控,甚至能做到无感升级。

3. 服务熔断与降级 (Circuit Breaking & Fallback)

这是系统的“保险丝”和“备胎”。

  • 场景:下游的“推荐服务”挂了,或者响应巨慢。如果没有保护,线程池会被耗尽,导致整个商城瘫痪(雪崩效应)。
  • 作用
    • 熔断:网关检测到错误率或响应时间超过阈值,直接“拉闸”,后续请求不再转发,直接快速失败。
    • 降级:拉闸的同时,网关可以直接返回一个预设的静态数据(比如默认推荐列表、缓存数据),而不是让用户看到冷冰冰的 502 报错。
  • 价值防止雪崩,保住核心业务(如下单)不受非核心业务(如评论、推荐)故障的影响。

4. 参数校验与请求重写 (Validation & Rewriting)

这是网关的“安检”和“整容”功能。

  • 场景
    • 校验:用户传了个年龄 -18,或者必填字段 orderId 为空。这种垃圾数据不该进后端服务浪费资源。
    • 重写:前端传的 URL 是 /v1/users/me,后端实际需要的是 /api/v1/user/{id}
  • 作用:网关在转发前,先检查参数合法性(正则匹配、类型检查)。如果不合法直接拦截。同时支持修改路径(StripPrefix)、添加或删除 Header。
  • 价值减轻后端负担,解耦前后端接口定义。

5. 全链路可观测性 (Observability & Auditing)

这是系统的“黑匣子”。

  • 场景:用户投诉说“下单失败了”,开发问“是哪个环节挂了?是网络超时还是代码报错?”
  • 作用
    • 日志:记录所有进出请求的详细信息(耗时、状态码、入参)。
    • 监控:实时统计 QPS、延迟分布、错误率。
    • 链路追踪:生成并透传 TraceID,串联起从网关到微服务的完整调用链。
  • 价值故障排查神器。没有这个,微服务排错就是在大海捞针。

6. 响应缓存 (Response Caching)

这是网关的“小抄”。

  • 场景:首页的轮播图、商品详情,几百万用户都在看,内容却是一样的。每次都打到数据库太亏了。
  • 作用:网关直接把后端返回的响应存起来(Redis 或本地内存)。下次同样的请求来了,网关直接返回缓存,根本不去打扰后端服务。
  • 价值削峰填谷,极大提升读性能,保护后端数据库。
功能模块核心价值形象比喻
协议转换兼容新旧系统,多语言互通同声传译
灰度发布降低上线风险,A/B 测试试衣间
熔断降级防止雪崩,兜底保障保险丝/备胎
参数校验过滤垃圾数据,保护后端安检员
可观测性故障定位,审计合规监控摄像头/黑匣子
响应缓存提升读取速度,减少后端压力便利店货架

总结

API 网关不是简单的转发代理,它是微服务架构的中央控制塔

  1. 统一入口:隐藏内部服务拓扑,前端只认网关地址。
  2. 鉴权:在网关层把非法请求挡在门外,保护脆弱的后端微服务。
  3. 限流熔断:防止突发流量把系统打垮,留得青山在,不怕没柴烧。

下次写网关代码时,记得把自己当成那个手握生杀大权的“看门大爷”,严谨一点,你的微服务才能活得久一点。

更多推荐