Skip to content

GateWay网关

针对所有请求进行统一鉴权、限流、熔断、日志,路由

核心概念

Predicate就是我们的匹配条件: 而Filter,就是可以理解为一个无所不能的拦截器。

  • Route(路由): 从客户端到后端服务的路径。它定义了请求应该被转发到哪个后端服务。每个 Route 包含一个唯一的 ID、一个目标 URI(后端服务地址)和一组条件(Predicates)和过滤器(Filters)。

  • Predicate(断言):一组条件,用来判断请求是否符合某个 Route。只有当请求满足这些条件时,才会被转发到对应的后端服务。常见的条件包括请求的路径、方法、头信息等。

  • Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

    yml
    spring:
      cloud:
        gateway:
          routes:
          - id: order_service_route
            uri: http://orderservice
            predicates:
            - Path=/api/orders
            filters:
            - AddRequestHeader=Authorization, Bearer token

请求流程

  • 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。
  • 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。
    • Filter在"pre“类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等
    • Filter在”post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制

项目构建

pom文件

xml
<!--新增gateway,不需要引入web和actuator模块-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--若使用Eureka为注册中心,无需引入,因为其自身依赖有负载均衡组件。若使用nacos为注册中心,需引入下方依赖,否则访问接口结果如下:-->
<!--
Whitelabel Error Page
This application has no configured error view, so you are seeing this as a fallback.

Fri Feb 23 14:31:00 CST 2024
[a7462b29-2] There was an unexpected error (type=Service Unavailable, status=503).
-->

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

yml文件

yml
server:
  port: 9527

# 网关配置
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
       - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
         #匹配后提供服务的路由地址(即要代理的路由地址),可以是http,ws(websockrt协议),也可以使用lb从服务中心获取
         uri: http://localhost:8001  
         predicates:
           #断言,路径相匹配的进行路由
           - Path=/payment/get/**   
 
       - id: payment_routh2
         uri: http://localhost:8001
         predicates:
           #断言,路径相匹配的进行路由
           - Path=/payment/lb/**   

#注册到Eureka中心
eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka

启动类

java
@SpringBootApplication
@EnableEurekaClient
public class GetwayMain {
    public static void main(String[] args) {
        SpringApplication.run(GetwayMain.class, args);
    }
}

动态路由

从注册中心中读取服务信息进行路由。

yml
server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        #定位器
        locator: 
          # 开启从注册中心动态创建路由的功能,利用微服务名进行路由,即lb://cloud-payment-service 
          enabled: true 
      routes:
      	#路由的ID,没有固定规则但要求唯一,建议配合服务名
        - id: payment_routh 
          #uri: http://localhost:8001,匹配后提供服务的路由地址
          #uri的协议为lb,表示启用Gateway的负载均衡功能(轮询),cloud-payment-service 为微服务的名字,如果只有一个服务可以绑定IP和端口号,但是有多个服务只能使用微服务名。
          uri: lb://cloud-payment-service 
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka

Predicate

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

yml
routes:
    - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
    #uri: http://localhost:8001   #匹配后提供服务的路由地址
    uri: lb://cloud-payment-service
    predicates:
        - Path=/payment/lb/**   #断言,路径相匹配的进行路由
        - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] #在时间之后
        - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] #在时间之前
        - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai] #在两时间之内 
        - Cookie=username,zhangshuai #携带Cookie,并且其是username=zhangshuai才能访问
        - Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
        - Host=**.atguigu.com #匹配请求的 Host(主机)是否符合指定的模式。
        - Method=GET #请求的方法是否为Get
        - Query=username, \d+ #查询参数中的 username 参数是否符合指定的正则表达式模式 \d+(正整数)。 

Filter

  • gateway的过滤器在controller之前,所以全局异常处理是对其不生效的。
  • 对于某些不需要走全局过滤器的路径而言,在配置文件中配置,filter中读取后逻辑处理即可
java
@Component
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //从request中可以获得请求的全部信息
        ServerHttpRequest request = exchange.getRequest();
        
        // 这里进行鉴权操作,如果鉴权失败,返回HTTP 401 Unauthorized状态码
        if (/* 鉴权失败 */) {
            return writeResponse(exchange.getResponse(),401,"token验证失败");
        }
        return chain.filter(exchange);
    }

    //Gateway 中可以配置多个过滤器,通过实现 Ordered 接口,可以指定一个特定的执行顺序。较小的值表示较高的优先级
    @Override
    public int getOrder() {
        return 0;
    }
    
    
    
    /**
     * 构建返回内容
     *
     * @param response ServerHttpResponse
     * @param code     返回码
     * @param msg     返回数据
     * @return Mono
     */
    protected Mono<Void> writeResponse(ServerHttpResponse response, Integer code, String msg) {
        JSONObject message = new JSONObject();
        message.put("code", code);
        message.put("msg", msg);
        byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.OK);
        // 指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

负载均衡

GateWay中使用SpringCloud LoadBalancer作为负载均衡。

java
public class CustomLoadBalancerConfiguration {
        @Bean
        ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
                                                LoadBalancerClientFactory loadBalancerClientFactory) {
                String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
                System.out.println("name: " + name);
                return new RandomLoadBalancer(loadBalancerClientFactory
                        .getLazyProvider(name, ServiceInstanceListSupplier.class),
                        name);
        }
}



@SpringBootApplication
//指定默认的负载均衡规则
@LoadBalancerClients(defaultConfiguration = CustomLoadBalancerConfiguration.class)
public class Getway {
    public static void main(String[] args) {
        SpringApplication.run(Getway.class, args);
    }
}

重试与超时

重试

通过配置LoadBalancer解决

yml
spring:
  application:
    name: gateway
  cloud:
    gateway:
      httpclient:
        connect-timeout: 5000  # 全局连接超时时间,单位为毫秒
        response-timeout: 30000  # 全局响应超时时间,单位为毫秒
    loadbalancer:
      # 开启负载均衡器的重试,Spring Cloud Loadbalancer没有内置HTTP客户端,因此没有超时设置
      retry:
        enabled: true
        #在其他服务实例上执行的重试次数,设置的是其他实例重试次数的总和(不包含第一次),PS:当实例数大于2时,第一个实例不作为重试实例,小于等于2时,第一个实例也是重试的实例
        max-retries-on-next-service-instance: 1
        #在同一服务实例上执行的重试次数
        max-retries-on-same-service-instance: 0
        # 是否所有请求都开启重试,默认仅重试Get
        retry-on-all-operations: false
     

超时

java
//添加一个全局过滤器,设置最高优先级,当后续出现错误后,就会调用onErrorResume中传入的lambda表达式
//如果需要单独针对timeout处理,对于lambda表达式的异常使用:if (e instanceof java.util.concurrent.TimeoutException)即可
public class GateExceptionFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange).onErrorResume(e -> {
            Map<String, String> map = new HashMap<>();
            map.put("errorCode", HttpStatus.REQUEST_TIMEOUT.toString());
            map.put("errorMessage", "请求超时,系统忙不过来,触发限流(sentinel+gataway整合Case)");

            String responseBody = JSON.toJSONString(map);

            // 设置响应状态码和内容类型
            exchange.getResponse().setStatusCode(HttpStatus.REQUEST_TIMEOUT);
            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);

            // 将 JSON 字符串写入响应
            DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(responseBody.getBytes(StandardCharsets.UTF_8));
            return exchange.getResponse().writeWith(Mono.just(buffer));
        });
    }

    @Override
    public int getOrder() {
        return -1; // 设置过滤器的优先级,数字越小优先级越高
    }
}


@Configuration
public class GatewayExceptionConfiguration {

    @Bean
    public GateExceptionFilter gateExceptionFilter() {
        return new GateExceptionFilter();
    }
}