Spring Cloud Gateway简单使用


一、简介

Gateway网关是我们服务的守门神,所有微服务的统一入口。Spring Cloud Gateway 是 Spring Cloud的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。在Gateway之前,SpringCloud并不自己开发网关,可能是觉得Netflflix公司的Zuul不行吧,然后自己就写了一个,也是替代Netflflix Zuul。其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

本身也是一个微服务,需要注册到Eureka。

官网:

https://spring.io/projects/spring-cloud-gateway

1、功能特点:

  • 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
  • 动态路由
  • Predicates 和 Filters 作用于特定路由
  • 集成 Hystrix 断路器
  • 集成 Spring Cloud DiscoveryClient
  • 简单好用的 Predicates 和 Filters
  • 限流
  • 路径重写
请添加图片描述
  • 不管是来自客户端的请求,还是服务内部调用。一切对服务的请求都可经过网关。
  • 网关实现鉴权、动态路由等等操作。
  • Gateway是我们服务的统一入口。
  • 鉴权,安全控制,⽇志统⼀处理,易于监控的相关功能。

2、术语解释

Route(路由):这是网关的基本模块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。

Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。

Filter(过滤器):这是org.springframework.cloud.gateway.fifilter.GatewayFilter的实例,我们可以使用它修改请求和响应。

3、网关技术

  • nginx Nginx (engine x) :是⼀个⾼性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。
  • zuul :是 Netflix 出品的⼀个基于 JVM 路由和服务端的负载均衡器。
  • spring-cloud-gateway: 是spring 出品的 基于spring 的⽹关项⽬,集成断路器,路径重写,性能⽐Zuul好。

二、快速开始

1、创建Spring boot工程

请添加图片描述

2、启动引导类开启注册中心Eureka客户端发现

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现服务
public class DemoGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoGatewayApplication.class, args);
    }
}

3、配置文件appliation.yml

# 端口号
server.port: 10010
# 应用名
spring.application.name: api-gateway
# 注册中心地址
eureka.client.service-url.defaultZone: http://8.131.239.157:10086/eureka/

4、编写路由规则

spring:
  cloud:
    gateway:
    # 路由si(集合)
      routes:
      	# id(唯一标识)
        - id: websocket_test
        # 路由服务地址(转发后地址)
          uri: http://127.0.0.1:9091
          order: 9000
        # 断言(判断哪些请求要被转发)
          predicates:
            - Path=/user/**
  • 将符合 path 规则的请求,路由到 uri 参数指定地址。
  • 举例http://localhost:10010/user/fifindById?id=1 路由转发到http://localhost:9091/user/fifindById?id=1
  • 访问路径中,必须包含路由规则的映射路径/user才会被路由

三、路由配置(转发)(predicates)

谓词(predicates):当满足条件在进行路由转发

在 Spring Cloud Gateway 中谓词实现 GatewayPredicate 接口。其中类名符合: XXXRoutepredicateFactory,其中 XXX 就是在配置文件中谓词名称。在上面示例中 Path=/demo/** 实际上使用的就是PathRoutePredicateFactory

请添加图片描述

所有的谓词都设置在 predicates 属性中,当设置多个谓词时取逻辑与条件,且一个谓 词只能设置一组条件,如果需要有个多条件,添加多个相同谓词。

1、Query属性

表示路径满足/system/**同时包含参数 abc。

predicates:
  - Path=/system/**
  - Query=abc

支持正则表达式jqk.

支持指定某个字段值abc,jqk.

abc=jqka 或 abc=jqkw 能满足谓词条件。

predicates:
  - Path=/system/**
  - Query=abc,jqk.

测试:

http://localhost:9000/system/one?abc=jqk

完整版:

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
        filters:
          - StripPrefix= 1

2、Header

表示请求头中必须包含的内容。

  • 参数名和参数值之间依然使用逗号。
  • 参数值要使用正则表达式。
  • 支持选择多个Header头信息。

如果 Header 只有一个值表示请求头中必须包含的参数。如果有两个值,第一个表示请求头必须包含的参数名,第二个表示请求头参数对应值。

predicates:
  - Header=Connection,keep-alive
  - Header=Cache-Control,max-age=0

完整版:

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0

3、Method

Method 表示请求方式。支持多个值,使用逗号分隔,多个值之间为 or 条件。

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST

4、RemoteAddr

允许访问的客户端地址

  • 要使用127.0.0.1,不要使用localhost。
spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1

5、Host

匹配请求中的Host信息。满足Ant模式。

?:匹配一个字符

*:匹配0个或多个字符

**:匹配0个或多个目录

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000

6、Cookie

要求请求中包含指定的Cookie名和满足特定正则要求的值。

  • 必须要有2个值,第一个包含的是参数名,第2个表示参数对应的值(正则表达式)。
spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*

7、Before

在指定时间点之前

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*
          - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]

8、After

在指定时间点之后

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*
          - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
          - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]

9、Between

必须在设定的范围时间内,才能进行路由转发

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
          - Query=abc
          - Header=Connection,keep-alive
          - Header=Cache-Control,max-age=0
          - Method=GET,POST
          - RemoteAddr=127.0.0.1
          - Host=127.0.0.1:9000
          - Cookie=age,.*
          - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
          - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
          - Between=2020-01-31T18:00:00.000+08:00[Asia/Shanghai], 2020-02-01T00:00:00.000+08:00[Asia/Shanghai]

10、Weight

设置服务转发的权重,用于限制某个的请求占比

语法:Weight=组名,负载均衡权重

suiyi:权重:20%

suiyi2:权重:80%

spring:
  cloud:
    routes:
      - id: suiyi
        uri: lb://demo-one
        predicates:
          - Path=/demo/**
          - Weight=group,2
        filters:
          - StripPrefix=1
          
      - id: suiyi2
        uri: lb://demo-two
        predicates:
          - Path=/demo/**
          - Weight=group,8
        filters:
          - StripPrefix=1

四、过滤器(拦截)(Filter)

1、简介

过滤器作为网关的其中一个重要功能,就是实现请求的鉴权。

执行顺序:

Spring Cloud Gateway 的 Filter 的执行顺序有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用。

请添加图片描述

Gateway自带过滤器有几十个,常见自带过滤器有:

官网地址:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories
请添加图片描述
过滤器名称说明
AddRequestHeader对匹配上的请求加上Header
AddRequestParameters对匹配上的请求路由
AddResponseHeader对从网关返回的响应添加Header
StripPrefix对匹配上的请求路径去除前缀
PrefixPath对匹配上的请求路径添加前缀

使用场景:

  • 请求鉴权:如果没有访问权限,直接进行拦截
  • 异常处理:记录异常日志
  • 服务调用时长统计

2、系统内置过滤器使用

**过滤器类型:**Gateway有两种过滤器

  • 局部过滤器:只作用在当前配置的路由上。
  • 全局过滤器:作用在所有路由上。

2.1 添加响应头(AddResponseHeader)

对输出的响应设置其头部属性名称为i-love,值为itheima。

配置文件更新:

# 往响应过滤器中加入信息
   - AddResponseHeader: i-love,itheima

全部配置:

spring:
  cloud:
    gateway:
      # 全局过滤器配置
      default-filters:
     # 往响应过滤器中加入信息
        - AddResponseHeader: i-love,itheima
        
        - AddRequestParameters:
        - AddRequestHeader:
        - StripPrefix:
        - PrefixPath:

结果展示:

请添加图片描述

2.2 增加路由配置(PrefixPath)

在gateway中可以通过配置路由的过滤器PrefifixPath实现映射路径中的前缀添加。可以起到隐藏接口地址的作用,避免接口地址暴露。

# 请求地址添加路径前缀过滤器 
filters:
  - PrefixPath=/user

完整配置:

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route # 路由id,可以随意写 
          # 代理服务地址;lb表示从Eureka中获取具体服务 
          uri: lb://user-service 
          # 路由断言,配置映射路径 
          predicates:
            - Path=/** 
          # 请求地址添加路径前缀过滤器 
          filters:
            - PrefixPath=/user

结果展示:

配置访问地址路由地址
PrefifixPath=/userlocalhost:10010/findById?id=1localhost:9091/user/findById?id=1
PrefifixPath=/user/abclocalhost:10010/findById?id=1localhost:9091/user/abc/findById?id=1

2.3 删除路由配置(StripPrefix)

在gateway中通过配置路由过滤器StripPrefifix,实现映射路径中地址的去除。通过StripPrefifix=1来指定路由要去掉的前缀个数。

如:路径/api/user/1将会被路由到/user/1

filters:
  # 去除路径前缀过滤器
  - StripPrefix=1

完整配置文件

spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route # 路由id,可以随意写
          # 代理服务地址;lb表示从Eureka中获取具体服务
          uri: lb://user-service
          # 路由断言,配置映射路径
          predicates:
            - Path=/**
          # 请求地址添加路径前缀过滤器
          filters:
            # 去除路径前缀过滤器
            - StripPrefix=1

访问效果:

配置访问地址路由地址
StripPrefix=1localhost:10010/api/user/findById?id=1localhost:9091/user/findById? id=1
StripPrefix=2localhost:10010/aa/api/user/findById? id=1localhost:9091/user/findById? id=1

2.4 AddRequestHeader

添加请求头参数,参数和值之间使用逗号分隔

filters:
  - StripPrefix= 1
  - AddRequestHeader=MyHeader,jqk

2.5 AddRequestParameter

添加请求表单参数,多个参数需要有多个过滤器。

filters:
  - AddRequestParameter=name,bjsxt
  - AddRequestParameter=age,123

2.6 DedupeResponseHeader

对指定响应头去重复。

语法:DedupeResponseHeader=响应头参数 响应头参数,strategy

可选参数 strategy 可取值:

  • RETAIN_FIRST:默认值,保留第一个

  • RETAIN_LAST:保留最后一个。

  • RETAIN_UNIQUE:保留唯一的,出现重复的属性值,会保留一个。例如有两个 My:bbb 的属性,最后会只留一个。

filters:
  - StripPrefix= 1
  - DedupeResponseHeader=My Content-Type,RETAIN_UNIQUE

2.7 CircuitBreaker

实现熔断时使用,支持 CircuitBreaker 和 Hystrix 两种

2.8 FallbackHeaders

可以添加降级时的异常信息

2.9 RequestRateLimiter

限流过滤器

2.10 RedirectTo

重定向。

有两个参数,status 和 url。其中 status 应该 300 系列重定向状态码

2.11 RemoveRequestHeader

删除请求头参数

2.12 RemoveResponseHeader

删除响应头参数

2.13 RemoveRequestParameter

删除请求参数

2.14 RewritePath

重写请求路径

2.15 RewriteResponseHeader

重写响应头参数

2.16 SaveSession

如果项目中使用Spring Security和Spring Session整合时,会使用到此属性。

2.17 SecureHeaders

具有权限验证时,建议的头信息内容。

2.18 Retry

设置重试次数

功能和StripPrefix类似。

2.19 RequestSize

请求的最大大小。包含maxSize参数,可以有单位'KB''MB'默认为B。

2.20 ModifyRequestBody

修改请求体内容

2.21 ModifyResponseBody

修改响应体内容

2.22 SetPath

当请求路径为/red/blue时,会将/blue发送给下游。

spring:
  cloud:
    routes:
      - id: system
        uri: lb://system
        predicates:
          - Path=/red/{segment}
        filters:
          - SetPath=/{segment}

2.23 SetRequestHeader

替换请求参数头。

2.24 SetResponseHeader

替换相应头参数

2.25 SetStatus

设置相应状态码

3、自定义全局过滤器

放行:

//12. 放⾏
return chain.filter(exchange);

拦截:

//7. 响应中放⼊返回的状态吗, 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//8. 返回
return response.setComplete()

3.1 假如token为null,则拦截

import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Configuration
public class MyGlobalFileter implements GlobalFilter, Ordered {


    /**
     * 自定义过滤器规则
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("-----------------全局过滤器MyGlobalFilter---------------------");
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     * 定义过滤器执行顺序
     * 返回值越小,越靠前执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

3.2 IP过滤器

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;

@Component
public class IPFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("经过IP过滤器");
        ServerHttpRequest request = exchange.getRequest();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        System.out.println("请求的IP地址为:" + remoteAddress.getHostName());

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

3.3 请求url过滤器

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
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 UrlFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("经过url过滤器");
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("请求的url为:" + path);
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

4、自定义可配置过滤器(添加到配置文建)

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

@Component
public class MyRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<MyRouteGatewayFilterFactory.Config> {
    public MyRouteGatewayFilterFactory() {
        super(MyRouteGatewayFilterFactory.Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("在这个位置写点东西传递进来的 name: " + config.getName() + ",age:" + config.getAge());
                return chain.filter(exchange);
            }

            @Override
            public String toString() {
                return GatewayToStringStyler.filterToStringCreator(MyRouteGatewayFilterFactory.this).append("name", config.getName()).toString();
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name");
    }

    public static class Config {
        private String name;
        private int age;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public Config() {
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

添加到配置文件中(application.yaml)

spring:
  application:
    name: sysgateway
  cloud:
    routes:
    #   自定义配置
      - id: myFilter
        uri: lb:/jqk
        predicates:
          - Path=/project/**
        filters:
          - StripPrefix=1
          - name: MyRoute
            args:
           # 传递的参数(name、age)
              name: hello
              age: 12

五、其它配置

1、网关自动映射处理

  • 只要请求地址符合规则:http://gatewayIp:gatewayPort/微服务名称/微服务请求地址
  • 网关自动映射。把请求地址转发到 http://微服务名称/微服务请求地址
  • 如:有微服务,命名为ribbon-app-service
  • 商业开发中:enabled一般不设置,默认为false。避免不必要的自动转发机制。

案例:

请求地址:http://localhost:9999/ribbon-app-service/getArgs?name=admin&age=20
自动转发到:http://ribbon-app-service/getArgs?name=admin&age=20

配置文件

spring:
  application:
    name: sysgateway
  cloud:
    gateway:
      discovery: # 配置网关发现机制
        locator: #  配置处理机制
          enabled: true # 开启网关自动映射处理机制
          lower-case-service-id: true # 开启微服务名称小写转换。Eureka对服务名管理默认全大写。

2、动态路由

#	路由到执行IP
uri: http://127.0.0.1:9091
#	根据服务名称进行路由(从配置中心获取指定IP)
uri: lb://user-service
  • 路由配置中uri所用的协议为lb时,gateway将把user-service解析为实际的主机和端口,并通过Ribbon进行负载均衡。
  • 应该是根据服务名称,去Eureka注册中心查找服务对应的所有实例列表,然后进行动态路由!

测试日志:

请添加图片描述

这次gateway进行路由时,会利用Ribbon进行负载均衡访问。日志中可以看到使用了负载均衡器。

请添加图片描述

3、跨域配置

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # ⽀持的⽅法
              - GET
              - POST
              - PUT
              - DELETE

4、记录执行耗时

//	将开始时间放入请求中
exchange.getAttributes().put("startTime", System.currentTimeMillis());
//	计算执行耗时
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    Long startTime = exchange.getAttribute("startTime");
    Long executeTime = System.currentTimeMillis() - startTime;
}));

完整版:

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class UrlFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("经过url过滤器");
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("请求的url为:" + path);
      	exchange.getAttributes().put("startTime", System.currentTimeMillis());

        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null) {
                long executeTime = System.currentTimeMillis() - startTime;
                log.info("请求时间:{}", exchange.getRequest().getURI().getRawPath() + "d" + executeTime + "ms");
            }
        }));
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

5、exchange代码信息

//  获取请求头信息
HttpHeaders headers = exchange.getRequest().getHeaders();
String first = exchange.getRequest().getHeaders().getFirst("Content-type");
ServerHttpRequest build = exchange.getRequest().mutate().header("Content-type","json").build();
//  获取请求方式
String name = exchange.getRequest().getMethod().name();
//  获取请求URL地址信息
String host = exchange.getRequest().getURI().getHost();
String path1 = exchange.getRequest().getURI().getPath();
String rawPath = exchange.getRequest().getURI().getRawPath();
//  获取请求属性信息
exchange.getAttributes().put("startTime",123123);
Long startTime1 = exchange.getAttribute("startTime");
//  获取返回体
ServerHttpResponse response = exchange.getResponse();

六、网关限流

1、简介

限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每⼀个微服务中做限流操作,但是如果有了⽹关,那么就可以在⽹关系统做限流,因为所有的请求都需要先通过⽹关系统才能路由到微服务中。

请添加图片描述

2、令牌桶算法简介

令牌桶算法是⽐较常⻅的限流算法之⼀,⼤概描述如下:

  1. 所有的请求在处理之前都需要拿到⼀个可⽤的令牌才会被处理;
  2. 根据限流⼤⼩,设置按照⼀定的速率往桶⾥添加令牌;
  3. 桶设置最⼤的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
  4. 请求达到后⾸先要获取令牌桶中的令牌,拿着令牌才可以进⾏其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
  5. 令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证⾜够的限流
请添加图片描述

3、代码实现

3.1 pom.xml依赖

spring cloud gateway 默认使⽤redisRateLimter限流算法来实现。所以我们要使⽤⾸先需要引⼊redis的依赖。

<!-- redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

3.2 定义KeyResolver

GatewayApplicatioin引导类中添加如下代码,KeyResolver⽤于计算某⼀个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现服务
public class DemoGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoGatewayApplication.class, args);
    }

    //    定义一个KeyResolver
    @Bean
    public KeyResolver ipKeyResolver() {
        return new KeyResolver() {
            @Override
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
            }
        };
    }
}

3.3 修改配置

修改application.yml中配置项,指定限制流量的配置以及REDIS的配置。

filters:
  - PrefixPath=/test
  - name: RequestRateLimiter
    args:
      key-resolver: "#{@ipKeyResolver}"
      redis-rate-limiter.replenishRate: 1
      redis-rate-limiter.burstCapacity: 1
  • burstCapacity:令牌桶总容量。

  • replenishRate:令牌桶每秒填充平均速率。

  • key-resolver:⽤于限流的键的解析器的 Bean 对象的名字。它使⽤ SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

通过在 replenishRate 和中设置相同的值来实现稳定的速率 burstCapacity 。设置 burstCapacity ⾼于时,可以允许临时突发 replenishRate 。在这种情况下,需要在突发之间允许速率限制器⼀段时间(根据 replenishRate ),因为2次连续突发将导致请求被丢弃( HTTP 429 - Too Many Requests key-resolver: "#{@userKeyResolver}" ⽤于通过SPEL表达式来指定使⽤哪⼀个KeyResolver

如上配置:

  • 表示 ⼀秒内,允许 ⼀个请求通过,令牌桶的填充速率也是⼀秒钟添加⼀个令牌。

  • 最⼤突发状况 也只允许 ⼀秒内有⼀次请求,可以根据业务来调整 。

3.4 最终配置:

spring:
  application:
    name: sysgateway
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # ⽀持的⽅法
              - GET
              - POST
              - PUT
              - DELETE
    routes:
      - id: goods
        uri: lb://goods
        predicates:
          - Path=/goods/**
        filters:
          - StripPrefix= 1
          - name: RequestRateLimiter #请求数限流 名字不能随便写
            args:
              key-resolver: "#{@ipKeyResolver}"
              redis-rate-limiter.replenishRate: 1
              redis-rate-limiter.burstCapacity: 1
      - id: system
        uri: lb://system
        predicates:
          - Path=/system/**
        filters:
          - StripPrefix= 1
    # 配置Redis 127.0.0.1可以省略配置
    redis:
      host: 192.168.200.128
      port: 6379
server:
  port: 9101
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka
  instance:
    prefer-ip-address: true
posted @ 2022-08-03 23:53  ah_lydms  阅读(234)  评论(0编辑  收藏  举报