分布式微服务--GateWay(过滤器及使用Gateway注意点)

发布于:2025-08-08 ⋅ 阅读:(11) ⋅ 点赞:(0)

前言、Spring Cloud Gateway 与 Web 依赖冲突

    <!-- 下面两个依赖不能同时使用   -->
      <!-- Gateway 组件 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.0.0.RELEASE</version>
        </dependency>
        <!-- springboot-web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

1. 现象

  • 在使用 spring-cloud-starter-gateway 时,如果项目同时引入了 spring-boot-starter-web(传统的 Servlet Web 依赖),两者会冲突。

  • 主要表现为:启动时端口被占用、DispatcherServlet 与 Netty 服务器冲突,导致服务无法正常启动。

2. 原因

  • Spring Cloud Gateway 基于 Spring WebFlux,使用的是 Netty 作为底层服务器(响应式非阻塞)。

  • spring-boot-starter-web 是基于传统的 Servlet,使用的是 Tomcat 容器(阻塞模型)。

  • 两种模型底层不兼容,不能同时启动。

3. 解决方案

  • 如果要使用 Gateway,项目中应使用 spring-boot-starter-webflux 替代 spring-boot-starter-web

  • 保证只用一个 Web 服务器容器(Netty)。

  • 不能同时引入 spring-boot-starter-webspring-cloud-starter-gateway

4. 注意事项

  • 如果项目有控制器需要兼容传统 MVC,推荐拆分服务:

    • API 网关用 Spring Cloud Gateway + WebFlux

    • 后端服务用 Spring MVC (spring-boot-starter-web)

  • 避免同时使用两个 Web 依赖。

一、三种路由配置方式详解与对比:自动发现、静态 URI 与服务名路由(lb)

✅ 1、三种 Gateway 配置方式说明

1️⃣ 自动服务发现路由(基于注册中心,比如 Nacos)

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

✅ 说明:

  • 开启后 Gateway 会自动将注册中心(如 Nacos)中注册的服务生成路由。

  • 不需要手动写 routes

  • 访问路径格式固定为:http://网关地址/服务名/xxx

  • 实际转发到的服务为注册中心中对应服务实例

✅ 适用场景:

  • 微服务较多,想简化网关配置。

  • 不需要定制复杂路由规则。

✅ 是否用到 Nacos:

✔ 是的,必须使用 Nacos / Eureka 等注册中心,才能自动发现服务。


2️⃣ 手动配置路由(静态 URI)

spring:
  cloud:
    gateway:
      routes:
        - id: baidu
          uri: http://www.baidu.com/
          predicates:
            - Path=/baidu/**

✅ 说明:

  • 手动指定目标 URI。

  • uri 是静态地址,不能做服务发现。

  • Path=/baidu/** 表示匹配 /baidu/* 的请求会被转发到 http://www.baidu.com

✅ 适用场景:

  • 转发到外部服务,如第三方接口、静态页面。

  • 不使用服务发现(非微服务调用)。

✅ 是否用到 Nacos:

❌ 不需要 Nacos。


3️⃣ 手动配置路由(基于服务名,使用负载均衡)

 基于服务名,就必须用lb:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service/
          predicates:
            - Path=/user/**

✅ 说明:

  • lb://user-service 表示通过 注册中心获取 user-service 实例,并使用负载均衡访问

  • lb: 是 load balancer 的前缀,Spring Cloud Gateway 会自动接入负载均衡策略。

✅ 适用场景:

  • 想要自定义路由规则 + 使用 Nacos 服务发现。

  • 比如 /user/** 映射到 user-service 微服务。

✅ 是否用到 Nacos:

✔ 是的,需要注册中心。

注意:

        Spring Cloud Gateway 使用的是 Spring Cloud LoadBalancer,默认策略是 RoundRobinLoadBalancer(轮询算法)。如果想使用随机就去定义下列代码即可

@Configuration
public class ProducerLoadBalancerConfig {
 
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
        Environment environment,
        LoadBalancerClientFactory factory) {
    
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        //随机
        return new RandomLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
        //轮询
        return new RoundRobinLoadBalancer(factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
          return new MyCustomLoadBalancer(); // 自定义负载均衡策略
    }
//也可以自定义负载均衡策略
public class MyCustomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
 
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceId;
 
    public MyCustomLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> provider, String serviceId) {
        this.serviceInstanceListSupplierProvider = provider;
        this.serviceId = serviceId;
    }
 
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        return serviceInstanceListSupplierProvider.getIfAvailable()
                .get()
                .next()
                .map(serviceInstances -> {
                    // TODO: 在这里写你的选择逻辑(比如权重、元数据等)
                    if (serviceInstances.isEmpty()) {
                        return new EmptyResponse();
                    }
                    // 简单示例:随机
                    ServiceInstance instance = serviceInstances.get(ThreadLocalRandom.current().nextInt(serviceInstances.size()));
                    return new DefaultResponse(instance);
                });
    }
}
 
}

  想了解更多负载均衡以及如果我不同服务想用不同负载均衡策略怎么办?

        可以去看笔者的这两篇博客这里不多赘述

分布式微服务--万字详解 微服务的各种负载均衡全场景以注意点-CSDN博客

分布式微服务--Ribbon 与 Spring Cloud LoadBalancer 区别 (五)-CSDN博客


✅ 2、三种配置方式对比总结

配置方式 依赖 Nacos 是否动态发现 路由配置方式 场景
discovery.locator.enabled ✅ 是 ✅ 自动发现服务 不写 routes 微服务多,简化配置
routes + 静态 URI(http://) ❌ 否 ❌ 静态目标 手动写 routes 转发外部接口
routes + 动态 URI(lb://) ✅ 是 ✅ 使用服务名 手动写 routes 自定义规则 + 服务发现

✅ 3、常见配置误区提醒

  • 开启 discovery.locator.enabled: true 后,如果你写了 routes,它们可以 共存,但 routes 优先级更高

  • lb://xxx 必须启用服务发现并引入 spring-cloud-starter-loadbalancer 或等效功能(大多数 starter 默认引入)。

  • 若使用 Nacos,需要引入如下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

二、uri中结尾加不加/的区别

🌐 Gateway 路由中 uri 是否加 / 的影响

📌 背景示例

spring:
  cloud:
    gateway:
      routes:
        - id: baidu
          uri: http://www.baidu.com
          predicates:
            - Path=/baidu/**

访问 http://localhost:8080/baidu/index 时:

配置方式 实际转发地址
uri: http://www.baidu.com(无 / http://www.baidu.com/baidu/index
uri: http://www.baidu.com/(有 / http://www.baidu.com/index

✅ 原因说明

  • 请求 /baidu/index 命中路由,剩余路径会拼接到 uri 后。

  • 如果 uri 没有 /,会直接拼接整个 /baidu/index

  • 如果 uri/,则拼接去掉前缀后的部分(更合理)。


🛠 推荐写法(最佳实践)

使用 StripPrefix 过滤器去除前缀:

spring:
  cloud:
    gateway:
      routes:
        - id: baidu
          uri: http://www.baidu.com/
          predicates:
            - Path=/baidu/**
          filters:
            - StripPrefix=1

效果

  • 请求 /baidu/index

  • 实际转发:http://www.baidu.com/index


📝 总结建议

配置项 建议用法 说明
uri 结尾加 / 避免重复路径拼接
StripPrefix 根据路径前缀设置 去掉前缀,保持后端路径整洁

注意:为什么看起来uri结尾有/时,加不加filters:- StripPrefix=1,效果一样呢?

在这个例子中,两种配置的转发结果一致,是因为:

  • uri 带斜杠时,Gateway 的默认行为就是去除断言中定义的前缀(/baidu);
  • StripPrefix=1 在这里的作用与默认行为重合(都是去除 /baidu)。

在下面例子就有所不同了

例:多段前缀(如 /api/baidu/**

predicates:
  - Path=/api/baidu/**

# 无 StripPrefix + uri 带斜杠:去除完整前缀 /api/baidu,转发到 http://www.baidu.com/s?wd=test
# 有 StripPrefix=1 + uri 带斜杠:仅去除第一段 /api,转发到 http://www.baidu.com/baidu/s?wd=test

总结

  • 在你的特定例子中,两种配置效果表面相同(转发地址一致);
  • 但本质逻辑不同:前者依赖 uri 带斜杠的默认行为,后者依赖显式的 StripPrefix 过滤器;
  • 最佳实践:推荐使用 StripPrefix 过滤器,因为它更直观、可控,避免依赖 uri 斜杠的隐式规则(尤其在复杂路径场景下)。

🧪 三、支持的断言(Predicates)常用类型

Predicate 示例 说明
Path /user/** 请求路径匹配
Method GET, POST 请求方法匹配
Header "X-Request-Id", "^\d+$" 请求头匹配
Host **.example.com Host 匹配
After/Before/Between 时间限制访问 支持 ISO8601 时间格式

🧩 四、支持的过滤器(Filters)常用类型

Filter 示例 说明
AddRequestHeader AddRequestHeader=X-Request-color, blue 添加请求头
AddResponseHeader 同上 添加响应头
RewritePath RewritePath=/foo/(?<segment>.*), /$\{segment} 路径重写
StripPrefix StripPrefix=1 去掉路径前缀
Hystrix name=myHystrixCommand fallbackUri=forward:/fallback 熔断处理(Spring Cloud Netflix 需要依赖)

过滤器小例子

        下面的代码通过A使用服务名和Gateway调用到B的时候,会在这个请求添加请求头并且B中可以获取这个请求头的值

调用方A:(只需要在yml中添加 AddRequestHeader=X-Request-color,red)

server:
  port: 7009
spring:
  application:
    name: boyatop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: member
          uri: lb://member1
          predicates:
            - Path=/member/**
          filters:  #下面是过滤器的配置
            - StripPrefix=1
            - AddRequestHeader=X-Request-color,red #添加请求头

被调用方B:

//yml
server:
  port: 8081
spring:
  application:
    name: member
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
//启动类
@SpringBootApplication
public class MemberApplication {
    public static void main(String[] args) {
        SpringApplication.run(MemberApplication.class, args);
    }
}
//Controller类
@RestController
@Slf4j
public class headerController{
 @RequestMapping("/get")
    public String get(){
        return "hhh8081";
    }
    @GetMapping("/testgateway")
    public String testGateway(HttpServletRequest request) throws Exception {
         log.info("gateWay获取请求头X-Request -color:"   +request.getHeader("X-Request-color"));
          return "success";
    }
   @GetMapping("/testgateway2")
   public String testGateway(@RequestHeader("X-Request-color") String color) throws Exception {
           log.info("gateWay获取请求头X-Request -color:"+color);
           return "success";
    }
}

🔍五、自定义过滤器(GlobalFilter)

一、两者概念区别

对比项 自定义过滤器(GatewayFilter) 全局过滤器(GlobalFilter)
作用范围 只作用于配置中的指定路由 作用于所有请求(全局)
实现接口 GatewayFilter + AbstractGatewayFilterFactory GlobalFilter + Ordered
是否需要 yml 配置 ✅ 是,必须在 application.yml 的路由中配置 ❌ 否,不需要配置,自动生效
应用场景 针对某个路由的参数处理、Header 增加等 鉴权、日志、异常统一处理、限流等
触发机制 仅当匹配到某条路由才执行 每个请求都会执行

二、自定义过滤器(AbstractGatewayFilterFactory)示例

1️⃣ 编写自定义过滤器类:

@Component
public class AddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddHeaderGatewayFilterFactory.Config> {

    public static class Config {
        private String name;
        private String value;
        // getter / setter
    }

    public AddHeaderGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 添加请求头
            exchange.getRequest().mutate()
                    .header(config.name, config.value)
                    .build();
            return chain.filter(exchange);
        };
    }
}

2️⃣ yml 中使用该过滤器:

spring:
  cloud:
    gateway:
      routes:
        - id: user_route
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            #这个名称必须与AddHeaderGatewayFilterFactory所匹配否则匹配不上
            #也就是说自定义拦截器叫HhGatewayFilterFactory
            #下面也要写成Hh=name,value 
            - AddHeader=name,value 

3. 自定义过滤器和全局过滤器使用场景对比

需求 / 功能 推荐使用类型 说明
针对某个接口改请求头、参数 自定义过滤器 精准控制某条路由
日志记录(所有请求) 全局过滤器 统一处理
全局 token 鉴权 全局过滤器 所有路由都校验
自定义 header 注入 自定义过滤器 某个接口单独处理
异常处理 / 限流 全局过滤器 通用逻辑

🔍五、全局过滤器(GlobalFilter)

✅ 不需要写在 yml 中,所有请求默认都会走这个过滤器。

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if (StringUtils.isEmpty(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

这个全局过滤器实现了:

  • 检查请求头中是否包含 token

  • 如果没有,直接返回 401 状态码

✅ 实现 Ordered接口

5.1、核心区别
是否实现 Ordered 接口 执行顺序可控 说明
❌ 未实现 ❌ 不可控(默认顺序) 无法确定具体执行顺序
✅ 实现 ✅ 可控 可通过 getOrder() 指定优先级

5.2、Ordered 接口作用

Ordered 是 Spring 提供的一个接口:

  • 用于定义组件(如 Filter、Interceptor 等)的执行顺序

  • 数字越小,优先级越高(越早执行)


5.3、代码示例对比
5.3.1 不实现 Ordered(默认顺序)
@Component
public class AuthGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 过滤逻辑
        return chain.filter(exchange);
    }
}
  • 执行顺序默认;

  • 多个过滤器顺序无法手动控制。


5.3.2 实现 Ordered(手动控制顺序)
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 过滤逻辑
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1; // 数字越小,优先级越高
    }
}
  • 可以明确控制执行顺序;

  • 常用于权限验证、日志记录等需要优先执行的逻辑。


5.4、getOrder() 的优先级说明
数值 优先级说明 常用场景
-1 高优先级 权限校验
0 默认优先级 日志记录等常规
1 比默认稍晚 后置处理
100 明显靠后 最后处理逻辑

✅ 实践建议:
  • 需要控制顺序 ➜ 实现 Ordered

  • 不关心顺序 ➜ 可不实现 Ordered

  • 权限校验类 Filter ➜ 推荐 getOrder() = -1 或更小


📄六、断言与过滤器

6.1  一句话区分:

  • 断言(Predicate):判断「是否进入」某条路由。

  • 过滤器(Filter):在进入或返回时「做点处理」。

6.2 更通俗的比喻:

把 Gateway 想象成一个大门口

  • 断言 = 门口的保安:检查条件是否满足,比如:路径是不是 /user/**,IP 是不是白名单,如果不符合,就不让进。

  • 过滤器 = 安检员+引导员:你进门之后,它检查你有没有带违禁品(请求修改),或者走的时候送你一张发票(响应修改)。


6.3 什么时候用哪个?
作用 用断言 用过滤器
控制哪些请求能进来 ✅ 是的,比如判断路径、方法、参数等 ❌ 不负责判断入口
修改请求内容(如添加请求头) ❌ 做不到 ✅ 过滤器可以
修改响应内容(如统一返回格式) ❌ 做不到 ✅ 过滤器可以
鉴权(Token 检查) ❌ 不负责 ✅ 通常放过滤器中
路由转发前的数据加工 ❌ 不处理 ✅ 可以改路径、改头信息等

🧰 七、常见问题及排查技巧

问题 解决方式
请求不转发 检查 routes 中的 path 和 uri 是否正确
路径不匹配 加上 StripPrefix 或 RewritePath
服务名不识别 使用 Nacos 并开启服务发现
过滤器无效 全局:实现 GlobalFilter;局部:添加在路由 filters 中
请求头 token 丢失 加日志观察请求流转路径;部分服务可能会过滤头部字段

🧠 八、总结

  • Spring Cloud Gateway 是基于 WebFlux 的高性能网关组件

  • 推荐在微服务中作为 API 网关统一入口使用

  • 配置灵活,可通过 YML 实现路径转发、负载均衡

  • 支持断言(匹配规则)和过滤器(增强功能)

  • 可结合 Nacos、OAuth2 等组件构建更复杂的网关服务


网站公告

今日签到

点亮在社区的每一天
去签到