SpringCloud Gateway网关

案例1:环境搭建

可以复制 Service-A9001 改成Gateway-C9009
》1:创建工程导入依赖

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

注意

SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容,需要从工程的pom.xml中删除 web框架,在需要web框架的模块中添加

》2:配置启动类

package com.dev1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

》3:编写配置文件

server:
  port: 9009
spring:
  application:
    name: Gateway-C9009
  cloud:
    gateway:
      routes:
        - id: route1
          uri: http://127.0.0.1:9001
          predicates:
            - Path=/person/**
#        - id: route2
#          uri: http://127.0.0.1:9002
#          predicates:
#            - Path=/order-service/**

(1)id :我们自定义的路由 ID,保持唯一
(2)uri :目标服务地址
(3)predicates :路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默
认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
(4)filters :过滤规则,暂时没用

上面这段配置的意思是,配置了一个 id 为 persson-service的路由规则,当访问网关请求地址person 开头时,会自动转发到地址: http://127.0.0.1:9001/ 。

配置完成启动项目即可在浏览器访问进行测试,当我们访问地址 http://localhost:9009/person/find/2 时会展示页面展示如下

SpringCloud案例day05_spring cloud

案例2:路由规则

在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件
匹配到对应的路由

#路由断言之后匹配
spring:
cloud:
 gateway:
  routes:
  - id: after_route
   uri: https://xxxx.com
    #路由断言之前匹配
   predicates:
   - After=xxxxx
#路由断言之前匹配
spring:
cloud:
 gateway:
  routes:
  - id: before_route
   uri: https://xxxxxx.com
   predicates:
   - Before=xxxxxxx
#路由断言之间
spring:
cloud:
 gateway:
  routes:
  - id: between_route
   uri: https://xxxx.com
   predicates:
   - Between=xxxx,xxxx
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
spring:
cloud:
 gateway:
  routes:
  - id: cookie_route
   uri: https://xxxx.com
   predicates:
   - Cookie=chocolate, ch.p
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
spring:
cloud:
 gateway:
  routes:
  - id: header_route
   uri: https://xxxx.com
   predicates:
   - Header=X-Request-Id, \d+
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
spring:
cloud:
 gateway:
  routes:
  - id: host_route
   uri: https://xxxx.com
   predicates:
   - Host=**.somehost.org,**.anotherhost.org
#路由断言Method匹配,匹配的是请求的HTTP方法
spring:
cloud:
 gateway:
  routes:
  - id: method_route
   uri: https://xxxx.com
   predicates:
   - Method=GET
#路由断言匹配,{segment}为可变参数
spring:
cloud:
 gateway:
  routes:
  - id: host_route
   uri: https://xxxx.com
   predicates:
   - Path=/foo/{segment},/bar/{segment}
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含
foo,并且foo的值匹配ba.)
spring:
cloud:
 gateway:
  routes:
  - id: query_route
   uri: https://xxxx.com
   predicates:
   - Query=baz 或 Query=foo,ba.
   
#路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位
数即255.255.255.0 
spring:
cloud:
 gateway:
  routes:
  - id: remoteaddr_route
   uri: https://example.org
   predicates:
   - RemoteAddr=192.168.1.1/24

案例3:动态路由

和zuul网关类似,在SpringCloud GateWay中也支持动态路由:即自动的从注册中心中获取服务列表并
访问。
(1)添加注册中心依赖
在工程的pom文件中添加注册中心的客户端依赖(这里以Eureka为例)

 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

( 2)配置动态路由
修改 application.yml 配置文件,添加eureka注册中心的相关配置,并修改访问映射的URL为服务名称

server:
  port: 9009
spring:
  application:
    name: Gateway-C9009
  cloud:
    gateway:
      routes:
      - id: person-service
        uri: lb://Service-A9001
        predicates:
        - Path=/person/**
eureka:
  instance:
    prefer-ip-address: true #使用ip地址注册
    instance-id:  ${spring.cloud.client.ip-address}:${server.port} #向注册中心中注册服务id
  client:
    service-url:
      defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/

uri : uri以 lb: //开头(lb代表从注册中心获取服务),后面接的就是你需要转发到的服务名称

测试

http://localhost:9009/person/find

案例3:重写转发路径

SpringCloud案例day05_spring_02

在SpringCloud Gateway中,修改Path为/person-service/**

# http://127.0.0.1:9009/person-service/person/find/1 转发到
# http://127.0.0.1:9001/person-service/person/find/1
但后者不存在,只有

# http://127.0.0.1:9001/person/find/1 可以访问

(1)修改配置文件
修改 application.yml ,将匹配路径改为 /person-service/**

重新启动网关,我们在浏览器访问

http://127.0.0.1:9009/person-service/person/find/1

会抛出404。这是由于路由转发规则默认转发到

http://127.0.0.1:9001/person-service/person/find/1

(2) 添加RewritePath重写转发路径
修改 application.yml ,添加重写规则。

server:
  port: 9009
spring:
  application:
    name: Gateway-C9009
  cloud:
    gateway:
      routes:
      - id: service-1 #编号唯一
        uri: lb://Service-A9001
        predicates:
        - Path=/person-service/**
        filters:
        - RewritePath=/person-service/(?<segment>.*), /$\{segment}

路径变化如下

http://127.0.0.1:9009/person-service/person/find/1 转发到
http://127.0.0.1:9001/person/find/1 可以访问

案例4:微服务名称转发

http://localhost:9009/service-a9001/person/find/1 转发到
http://127.0.0.1:9001/person/find/1 可以访问

配置如下

server:
  port: 9009
spring:
  application:
    name: Gateway-C9009
  cloud:
    gateway:
      routes:
      - id: service-1 #编号唯一
        uri: lb://Service-A9001
        predicates:
        - Path=/person-service/**
        filters:
        - RewritePath=/person-service/(?<segment>.*), /$\{segment}
      discovery:
        locator:
          lower-case-service-id: true
          enabled: true

案例5:全局过滤器搭建

》1:自定义一个全局过滤器,实现 globalfilter , ordered接口

@Component
public class LoginFilter implements GlobalFilter,Ordered {

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
        return chain.filter(exchange); //继续向下执行
    }

    /**
     * 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
     */
    public int getOrder() {
        return 0;
    }
}

》2:访问测试

http://localhost:9009/service-a9001/person/find/1

案例6:鉴权中心

(1)自定义全局过滤器需要实现 GlobalFilter和Ordered接口。
(2)在 filter方法中完成过滤器的逻辑判断处理
(3)在 getOrder方法指定此过滤器的优先级,返回值越大级别越低
(4)ServerWebExchange 就相当于当前请求和响应的上下文,存放着重要的请求-响应属性、请求实例和响应实例等等。一个请求中的request,response都可以通过 ServerWebExchange 获取
(5)调用 chain.filter 继续向下游执行

/**
 * 自定义一个全局过滤器
 *      实现 globalfilter , ordered接口
 */
@Component
public class LoginFilter implements GlobalFilter,Ordered {

    /**
     * 执行过滤器中的业务逻辑
     *     对请求参数中的access-token进行判断
     *      如果存在此参数:代表已经认证成功
     *      如果不存在此参数 : 认证失败.
     *  ServerWebExchange : 相当于请求和响应的上下文(zuul中的RequestContext)
     */
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("执行了自定义的全局过滤器");
        //1.获取请求参数access-token
        String token = exchange.getRequest().getQueryParams().getFirst("access-token");
        //2.判断是否存在
        if(token == null) {
            //3.如果不存在 : 认证失败
            System.out.println("没有登录");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete(); //请求结束
        }
        //4.如果存在,继续执行
        return chain.filter(exchange); //继续向下执行
    }

    /**
     * 指定过滤器的执行顺序 , 返回值越小,执行优先级越高
     */
    public int getOrder() {
        return 0;
    }
}

测试

http://localhost:9009/service-a9001/person/find/1?access-token=abc
http://localhost:9009/service-a9001/person/find/1

案例7:网关限流(Filter)

SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。

》1:准备redis服务器
Redis-x64-4.0.2.3.zip
redisclient-客户端工具.zip

SpringCloud案例day05_微服务_03

打开监控
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S8bV6Ayv-1668667456838)(index_files/0cd2f043-5763-4fd2-bfd7-40042988f566.png)]
》2:导入 redis的依赖
首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:

  <!--监控依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--redis的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

》3:修改application.yml配置文件

server:
  port: 9009
spring:
  application:
    name: Gateway-C9009
  redis:
    host: localhost
    pool: 6379
    database: 0
  cloud:
    gateway:
      routes:
      - id: service-1 #编号唯一
        uri: lb://Service-A9001
        predicates:
        - Path=/person-service/**
        filters:
        - name: RequestRateLimiter
          args:
            # 使用SpEL从容器中获取对象
            key-resolver: '#{@pathKeyResolver}'
            # 令牌桶每秒填充平均速率
            redis-rate-limiter.replenishRate: 1
            # 令牌桶的上限
            redis-rate-limiter.burstCapacity: 3
        - RewritePath=/person-service/(?<segment>.*), /$\{segment}

在 application.yml 中添加了redis的信息,并配置了RequestRateLimiter的限流过滤器:
burstCapacity ,令牌桶总容量。
replenishRate ,令牌桶每秒填充平均速率。
key-resolver ,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

》4: 配置KeyResolver
为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。

package com.dev1;

@Configuration
public class KeyResolverConfiguration {

    /**
     * 编写基于请求路径的限流规则
     * //abc
     * //基于请求ip 127.0.0.1
     * //基于参数
     */
    @Bean
    public KeyResolver pathKeyResolver() {
        //自定义的KeyResolver
        return new KeyResolver() {
            /**
             * ServerWebExchange :
             *      上下文参数
             */
            public Mono<String> resolve(ServerWebExchange exchange) {
                return Mono.just(exchange.getRequest().getPath().toString());
            }
        };
    }
}

测试的时候使用以下地址不停刷新

http://localhost:9009/person-service/person/find/1?access-token=abc

不要使用

http://localhost:9009/service-a9001/person/find/1?access-token=abc

》5:基于请求参数或者ip限流的配置

增加

package com.dev1;


@Configuration
public class KeyResolver2Configuration {

    /**
     * 基于请求参数的限流
     *  请求 abc ? userId=1
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getQueryParams().getFirst("userId")
                //exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
        );
    }
}

修改

key-resolver: '#{@userKeyResolver}'

SpringCloud案例day05_微服务_04

大括号中就是我们的限流 Key,这边是IP,本地的就是localhost
timestamp: 存储的是当前时间的秒数,也就是System.currentTimeMillis() / 1000或者
Instant.now().getEpochSecond()
tokens: 存储的是当前这秒钟的对应的可用的令牌数量

Spring Cloud Gateway目前提供的限流还是相对比较简单的,在实际中我们的限流策略会有很多种情况,比如:
对不同接口的限流
被限流后的友好提示
这些可以通过自定义RedisRateLimiter来实现自己的限流策略,这里我们不做讨论