API网关模式:微服务世界的守门人

说实话,我第一次接触API网关是在2016年。那时候我们团队刚把单体应用拆成十几个微服务,结果发现客户端要调用一个订单详情页,居然得发5次HTTP请求。每个服务都有自己的认证逻辑,有的用JWT,有的用Session,简直乱成一锅粥。

后来我意识到,我们需要一个统一的入口。这就是API网关模式的核心价值——它就像微服务架构的守门人,所有外部请求都得先过它这一关。

什么是API网关?

说白了,API网关就是一个反向代理服务器。它位于客户端和后端服务之间,负责处理所有跨横切关注点的问题。我习惯把它比作公司的前台——你不需要知道每个部门在几楼,前台会帮你转接。

核心职责:

  • 路由转发:把请求分发到正确的后端服务
  • 限流熔断:保护后端不被流量冲垮
  • 认证鉴权:统一验证用户身份和权限
  • 协议转换:比如把HTTP转成gRPC

你想想看,如果没有网关,每个微服务都得自己实现认证、限流、日志这些功能。这不仅重复劳动,还容易出漏洞。我曾经在一个项目里看到,三个服务用了三种不同的限流算法,结果流量一上来,有的服务直接挂了,有的还在傻傻地处理请求。

Spring Cloud Gateway 实战

在Java生态里,Spring Cloud Gateway是我用得最多的网关方案。它基于WebFlux,性能比Zuul 1.x好不少。我记得有一次压测,同样的配置下,Gateway的吞吐量比Zuul高了将近30%。

先看一个最基础的路由配置:

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user_service", r -> r
            .path("/api/users/**")
            .filters(f -> f
                .addRequestHeader("X-Gateway", "true")
                .circuitBreaker(config -> config
                    .setName("userServiceCB")
                    .setFallbackUri("forward:/fallback/users")))
            .uri("lb://user-service"))
        .route("order_service", r -> r
            .path("/api/orders/**")
            .filters(f -> f
                .requestRateLimiter(config -> config
                    .setRateLimiter(redisRateLimiter())))
            .uri("lb://order-service"))
        .build();
}

这段代码做了三件事:把用户请求转发到user-service,给订单服务加了限流,还统一熔断保护。嗯,这里要注意,lb://前缀表示使用负载均衡,Gateway会从注册中心拉取服务列表。

限流熔断:别让一个服务拖垮整个系统

我在项目中遇到过最惨的一次事故:某个促销活动,流量突然暴涨10倍,结果订单服务扛不住,数据库连接池被打满。更糟的是,其他服务调用订单服务时也超时,线程被阻塞,最终整个系统雪崩。

从那以后,我对限流熔断特别敏感。Spring Cloud Gateway结合Resilience4j,可以做到精细化的流量控制:

@Bean
public RedisRateLimiter redisRateLimiter() {
    // 每秒10个请求,突发20个
    return new RedisRateLimiter(10, 20, 1);
}

@Bean
public Customizer<Resilience4JCircuitBreakerFactory> circuitBreakerCustomizer() {
    return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
        .circuitBreakerConfig(CircuitBreakerConfig.custom()
            .slidingWindowSize(10)        // 滑动窗口大小
            .failureRateThreshold(50)     // 50%失败率触发熔断
            .waitDurationInOpenState(Duration.ofSeconds(30))  // 熔断持续时间
            .build())
        .build());
}

我曾经踩过一个坑:限流阈值设得太死板。比如某个接口平时只有100QPS,我设了120的阈值。结果上线新功能后,正常流量涨到150,系统就开始误报限流。后来我改用自适应限流算法,根据历史数据动态调整阈值,这才消停。

避坑指南:限流和熔断的阈值一定要留余量。我曾经把熔断阈值设成80%,结果一次正常的流量波动就触发了熔断,导致大量请求被拒绝。建议初始值设成60%,运行一段时间后再根据实际数据调整。

认证鉴权:统一的安全防线

网关是认证的最佳位置。所有请求先在这里验证身份,后端服务就不用重复实现了。我习惯用JWT做认证,网关只负责校验token,不存储用户状态:

@Component
public class JwtAuthFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders()
            .getFirst("Authorization");
        
        if (token == null || !token.startsWith("Bearer ")) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        
        try {
            Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token.substring(7))
                .getBody();
            
            // 把用户信息传给下游服务
            ServerWebExchange mutatedExchange = exchange.mutate()
                .request(r -> r.header("X-User-Id", claims.getSubject()))
                .build();
            
            return chain.filter(mutatedExchange);
        } catch (Exception e) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
    
    @Override
    public int getOrder() {
        return -100; // 优先执行
    }
}

这里有个细节:我把解析出来的用户ID放到了请求头里,传给下游服务。这样每个微服务只需要从请求头拿用户信息,不用再自己解析JWT。既统一了认证逻辑,又减少了重复代码。

协议转换:让异构系统无缝对接

微服务架构里,不同服务可能用不同的通信协议。有的用HTTP,有的用gRPC,还有的用WebSocket。网关可以帮我们做协议转换。我最近一个项目就是前端用HTTP,后端部分服务用gRPC,网关在中间做转换:

@Component
public class GrpcProtocolConverter implements GatewayFilter {
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 把HTTP请求转成gRPC调用
        HttpRequest httpRequest = exchange.getRequest();
        String serviceName = extractServiceName(httpRequest.getURI());
        String methodName = extractMethodName(httpRequest.getURI());
        
        // 构建gRPC请求
        GrpcRequest grpcRequest = buildGrpcRequest(httpRequest);
        
        // 调用gRPC服务
        return grpcClient.call(serviceName, methodName, grpcRequest)
            .map(response -> {
                // 把gRPC响应转成HTTP响应
                ServerHttpResponse httpResponse = exchange.getResponse();
                httpResponse.getHeaders().setContentType(MediaType.APPLICATION_JSON);
                return httpResponse.writeWith(
                    Mono.just(httpResponse.bufferFactory()
                        .wrap(response.toByteArray())));
            });
    }
}

说实话,协议转换这块坑不少。比如gRPC的二进制格式和HTTP的JSON格式,序列化方式完全不同。我建议在网关层只做轻量级的转换,复杂的业务逻辑还是交给后端服务处理。

知识体系总览

下面这张图是我对API网关模式的理解,涵盖了核心功能和关键技术点:

API网关模式知识体系 客户端 API网关(Spring Cloud Gateway) 路由转发 路径匹配 + 负载均衡 限流熔断 Redis限流 + Resilience4j 认证鉴权 JWT校验 + 统一过滤 协议转换 HTTP ↔ gRPC 用户服务 订单服务 支付服务 库存服务 统一入口 · 统一安全 · 统一治理

总结

API网关模式不是银弹,但它确实解决了微服务架构中很多头疼的问题。我个人建议,如果你的系统超过5个微服务,或者需要对外提供API,那就应该考虑引入网关。

最后分享一个经验:网关层不要加太多业务逻辑。它应该只做路由、安全、治理这些横切关注点。业务逻辑一旦放进网关,调试和升级都会变得非常痛苦。我曾经见过一个项目,网关里塞了十几条业务规则,最后改一个需求要同时改网关和业务服务,简直是噩梦。

我的建议:先从小规模开始,只做路由和认证。等系统稳定了,再逐步加入限流、熔断、协议转换等功能。一口吃不成胖子,网关也一样。


公众号:蓝海资料掘金营,微信deep3321