为什么需要 Gateway 网关,它有什么作用?

#Java#面试八股

为什么需要 Gateway 网关,它有什么作用?

结论先行:网关是微服务的统一入口。它把「鉴权、限流、路由、灰度、跨域、日志、协议转换」这些横切逻辑从每个服务里抽出来,集中到入口处;同时屏蔽内部拓扑,让前端只面对一个域名。Spring Cloud Gateway 基于 WebFlux + Netty,用「路由(Route)= 断言(Predicate)+ 过滤器(Filter)」模型描述转发规则。

1. 没有网关时的问题

前端 ──┬──▶ order-service   (每处都要做鉴权、跨域、限流)
       ├──▶ user-service
       └──▶ stock-service   (服务扩缩容,前端要改地址)
问题无网关有网关
鉴权每个服务各写一套入口统一校验,内部服务信任头
跨域每个服务配 CORS网关统一处理
限流分散、标准不一按路由/用户统一限流
前端地址直连多个服务只连网关一个域名
灰度发布改 Nginx 或服务内逻辑网关按 header/权重路由
日志审计各服务格式不同入口统一记录

2. 三个核心概念

请求 ──▶ Route(id, uri, predicates, filters)
          │
          ├─ Predicate:Path=/api/order/**、Method=GET、Header=X-Tenant
          │             断言全部为 true 才匹配这条路由
          └─ Filter:StripPrefix=1、AddRequestHeader、RequestRateLimiter…
                     匹配后依次执行,再转发到 uri
概念作用示例
Route路由的最小单位:id + 目标 uri + 断言 + 过滤器order_route → lb://order-service
Predicate匹配条件,多个之间是「与」Path、Method、Header、Query、Weight
Filter请求/响应处理链,分 GatewayFilter 与 GlobalFilterStripPrefix、Retry、CircuitBreaker

uri 前缀 lb:// 表示走注册中心负载均衡,http:// 表示直连。

3. 配置示例

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: false          # 关闭自动按服务名生成路由,避免暴露内部服务
      routes:
        - id: order_route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
            - Method=GET,POST
          filters:
            - StripPrefix=1       # /api/order/1 → /order/1
            - AddRequestHeader=X-Gateway, rui-lin
        - id: user_route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 20
                redis-rate-limiter.burstCapacity: 40

4. 自定义全局过滤器:统一鉴权

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtVerifier jwtVerifier;

    public AuthGlobalFilter(JwtVerifier jwtVerifier) {
        this.jwtVerifier = jwtVerifier;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        if (path.startsWith("/api/auth/")) {          // 白名单
            return chain.filter(exchange);
        }
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !jwtVerifier.verify(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        // 把用户信息透传给下游,下游不再重复解析
        ServerHttpRequest request = exchange.getRequest().mutate()
                .header("X-User-Id", jwtVerifier.userId(token))
                .build();
        return chain.filter(exchange.mutate().request(request).build());
    }

    @Override
    public int getOrder() {
        return -100;                                  // 数字越小越先执行
    }
}

注意:网关返回 Mono<Void>,全程非阻塞,不要在过滤器里写阻塞式 JDBC 调用,否则会占满 Netty 的事件循环线程。

5. 网关不该做的事

  • 不做业务逻辑:网关只做与业务无关的横切关注点,否则每次业务变更都要发网关。
  • 不做重试风暴:对非幂等接口(POST 下单)开启重试会造成重复下单。
  • 不做大文件转发:会占用内存与连接,静态资源交给 Nginx/OSS。
  • 不承载数据库事务:网关无状态,不能成为业务数据的中转站。

6. 常见追问

追问一:Gateway 和 Nginx 怎么分工? Nginx 在接入层做 TLS 卸载、静态资源、四层转发;Gateway 在业务层做路由、鉴权、限流。常见链路是 客户端 → Nginx/LB → Gateway → 服务。

追问二:lb:// 是怎么工作的? ReactiveLoadBalancerClientFilter 解析 lb://service-name,从 ReactiveLoadBalancer 取一个实例,把 uri 替换成真实 http://ip:port 再转发。

追问三:网关的限流为什么常用 Redis? RequestRateLimiter 基于令牌桶,默认用 Redis + Lua 保证多网关实例共享计数。单机 KeyResolver 只能用本地内存,扩容后限流会失真。

追问四:网关如何做灰度? 用 Weight 断言按比例分流,或自定义 GlobalFilter 读 header(如 X-Gray: true)后改写 lb://service-name 为带版本标签的实例。

追问五:网关挂了怎么办? 网关必须多实例 + 前置 LB/Nginx 健康检查;它应保持无状态,不存会话,才能随时扩缩容。

小结

网关的存在理由是「收敛横切逻辑 + 屏蔽内部拓扑」。它把鉴权、限流、灰度从 N 个服务里搬到 1 个入口,代价是它成了关键路径,必须无状态、可水平扩展,并且绝不能塞业务代码。

笔记加载中…