第34课时 Spring Cloud Gateway

10.1 什么是微服务网关?

单体应用拆分成多个服务后,对外需要一个统一入口,解耦客户端与内部服务。
在这里插入图片描述
微服务网关是整个微服务API请求的入口,主要功能如下:

  1. 网关核心功能是路由转发,因此不要有耗时操作在网关上处理,让请求快速转发到后端服务上
  2. 网关还能做统一的熔断、限流、认证、日志监控等

在这里插入图片描述

10.2 为什么要有微服务网关?

上述所说的横切功能(以权限校验为例)可以写在三个位置:
1)每个服务自己实现一遍
2)写到一个公共的服务中,然后其他所有服务都依赖这个服务
3)写到服务网关的前置过滤器中,所有请求过来进行权限校验

第一种,缺点太明显,基本不用;

第二种,相较于第一点好很多,代码开发不会冗余,但是有两个缺点:
1)由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
2)由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。
而服务网关恰好可以解决这样的问题:
1)将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
2)如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。
下面是有网关时的微服务架构图
在这里插入图片描述

10.3 Spring Cloud Gateway简介

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。

优点:
1)性能强劲:是第一代网关Zuul的1.6倍
2)功能强大:内置了很多实用的功能,例如转发、监控、限流等
3)设计优雅,容易扩展

缺点:
1)其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
2)不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
3)需要Spring Boot 2.0及以上的版本,才支持

gateway与zuul的简单比较:
gateway使用的是异步请求,zuul是同步请求,gateway的数据封装在ServerWebExchange里,zuul封装在RequestContext里,同步方便调式,可以把数据封装在ThreadLocal中传递。

注意:这里需要注意一下gateway使用的netty+webflux实现,不要加入web依赖(不要引用webmvc),否则初始化会报错 ,需要加入webflux依赖。

10.4 Gateway核心概念

1、路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体,由ID、目标URI、Predicate集合、Filter集合组成。主要定义了下面的几个信息:

  1. id,路由标识符,区别于其他 Route。
  2. uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
  3. order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
  4. predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
  5. fifilter,过滤器用于修改请求和响应信息。

2、断言(Predicate)
Predicate(断言, 谓词) 是Java8中引入的函数式接口,提供了断言的功能,它可以匹配Http请求中的任何内容。只有断言都返回真,才会真正的执行路由。
断言就是说: 在 什么条件下 才能进行路由转发

3、过滤器(Filter)
过滤器就是在请求的传递过程中,为请求提供前置和后置的过滤
在这里插入图片描述
执行流程大体如下:
1)Gateway Client向Gateway Server发送请求
2)请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
3)然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
4)RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
5)如果过断言成功,由FilteringWebHandler创建过滤器链并调用
6)请求会一次经过PreFilter–微服务-PostFilter的方法,最终返回响应

10.5 代码示例

通过网关将请求转发到payment9001
第1步:创建一个 Gateway5001 的模块,导入相关依赖

<dependencies>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

第2步: 创建主类

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

第3步: 添加配置文件

server:
  port: 5001

spring:
  application:
    name: springcloud-gateway
  cloud:
    gateway:
      routes:          # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
        - id: payment_routh                 #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:9001        #匹配后提供服务的路由地址
          predicates:
            - Path=/gateway/payment/nacos/**         # 断言,路径相匹配的进行路由
          filters:      # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1 # 转发之前去掉1层路径

第4步: 启动项目, 并通过网关去访问微服务
在这里插入图片描述
改造一:上述案例在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从 注册中心获取此地址。

第1步:加入nacos依赖

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

第2步:在主类上添加注解

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

第3步:修改配置文件

server:
  port: 5002

spring:
  application:
    name: springcloud-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:    # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
        - id: payment_routh   #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:9001          #匹配后提供服务的路由地址
          uri: lb://nacos-payment-provider #匹配后提供服务的路由地址
          predicates:
            - Path=/gateway/payment/nacos/**        # 断言,路径相匹配的进行路由
          filters:      # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1 # 转发之前去掉1层路径

第4步:测试
在这里插入图片描述

10.6 断言(Predicate)

1、内置的断言工厂

SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:

1)基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

  1. AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
  2. BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
  3. BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]

2)基于Cookie的断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求cookie是否具有给定名称且值与正则表达式匹配。

-Cookie=chocolate, ch.

3)基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。

-Header=X-Request-Id, \d+

4)基于Host的断言工厂

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org

5)基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

-Method=GET

6)基于Path请求路径的断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

-Path=/foo/{segment}

7)基于Query请求参数的断言工厂

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

-Query=baz, ba.

8)基于路由权重的断言工厂

WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发

routes:
-id: weight_route1 uri: host1 predicates:
-Path=/product/**
-Weight=group3, 1
-id: weight_route2 uri: host2 predicates:
-Path=/product/**
-Weight= group3, 9

9)基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中

-RemoteAddr=192.168.1.1/24

可以在cmd 中使用curl发送请求
发送请求:curl http://localhost:5002/gateway/payment/nacos/1
添加cookie并发送请求:curl http://localhost:5002/gateway/payment/nacos/1 --cookie “username=yili”
添加请求头并发送请求 curl http://localhost:5002/gateway/payment/nacos/1 -H “X-Request-id:1234”
添加主机地址并发送请求 curl http://localhost:5002/gateway/payment/nacos/1 -H “Host:www.yiliedu.cn”

2、自定义断言工厂

自定义场景: 假设我们的应用仅仅让购买数量count在(0,5)之间的请求通过。
第1步:配置中增加
在这里插入图片描述
第2步:自定义一个断言工厂,继承 AbstractRoutePredicateFactory类,实现断言方法

@Component
public class CountRoutePredicateFactory extends AbstractRoutePredicateFactory<CountRoutePredicateFactory.Config> {

    //构造函数
    public CountRoutePredicateFactory() {
        super(CountRoutePredicateFactory.Config.class);
    }

    //读取配置文件的中参数值 给他赋值到配置类中的属性上
    public List<String> shortcutFieldOrder() {
        //这个位置的顺序必须跟配置文件中的值的顺序对应
        return Arrays.asList("minAge", "maxAge");
    }

    //断言逻辑
    public Predicate<ServerWebExchange> apply(CountRoutePredicateFactory.Config config) {
        return new Predicate<ServerWebExchange>() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                //1 接收前台传入的age参数
                String count = serverWebExchange.getRequest().getQueryParams().getFirst("count");

                //2 先判断是否为空
                if (StringUtils.isNotEmpty(count)) {
                    //3 如果不为空,再进行路由逻辑判断
                    int age = Integer.parseInt(count);
                    if (age < config.getMinCount() && age > config.getMinCount()) {
                        return true;
                    } else {
                        return false;
                    }
                }
                return false;
            }
        };
    }

    //配置类,用于接收配置文件中的对应参数
    @Data
    @NoArgsConstructor
    public static class Config {
        private int minCount;//0
        private int maxCount;//5
    }

//这是一个自定义的路由断言工厂类,要求有两个
//1 名字必须是 配置+RoutePredicateFactory
//2 必须继承AbstractRoutePredicateFactory<配置类>
第3步:启动测试

10.7 过滤器(Filter)

在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。
1)PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

2)POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。
3)GatewayFilter:应用到单个路由上。

4)GlobalFilter:应用到所有的路由上。
1、内置局部过滤器
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:
在这里插入图片描述
在这里插入图片描述

2、自定义局部过滤器

自定义局部过滤器需要实现AbstractGatewayFilterFactory(配置类)接口
自定义局部过滤器校验请求参数中的money是否在配置范围内

/自定义局部过滤器
@Component
public class MoneyGatewayFilterFactory
        extends AbstractGatewayFilterFactory<MoneyGatewayFilterFactory.Config> {

    //构造函数
    public MoneyGatewayFilterFactory() {
        super(MoneyGatewayFilterFactory.Config.class);
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("minMoney", "maxMoney");
    }

    //过滤器逻辑
    @Override
    public GatewayFilter apply(MoneyGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //1 接收前台传入的money参数
                String money = exchange.getRequest().getQueryParams().getFirst("money");
                //2 先判断是否为空
                if (StringUtils.isNotEmpty(money)) {
                    //3 如果不为空,再进行路由逻辑判断
                    int moneyValue = Integer.parseInt(money);
                    if (moneyValue < config.getMaxMoney() && moneyValue > config.getMinMoney()) {
                        System.out.println("consoleLog已经开启了....");
                    }
                }

                return chain.filter(exchange);
            }
        };
    }

    //配置类 接收配置参数
    @Data
    @NoArgsConstructor
    public static class Config {
        private int minMoney;
        private int maxMoney;
    }

3、内置全局过滤器

在这里插入图片描述

4、自定义全局过滤器

对于企业开发的一些业务功能处理,更多的还是需要我们自己编写过滤器来实现的
自定义全局过滤器需要实现GlobalFilter和Ordered接口

/**
 * 统一鉴权过滤器
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    //完成判断逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        //此处省略了查询用户已缓存在redis中的token,和请求参数中的token进行比对的逻辑
        //需要对访问登录认证微服务的请求放行
        if (!StringUtils.equals(token, "tokenFromRedis")) {
            System.out.println("鉴权失败");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //调用chain.filter继续向下游执行
        return chain.filter(exchange);
    }

    //顺序,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

10.8 网关限流

网关作为微服务架构的统一入口,在网关层面实现限流也比较广泛。
Spring Cloud Gateway整合Sentinel 实现网关限流,有两种维度的实现。
1)route 维度:即在 Spring cloud配置文件中配置的路由条目,资源名为对应的routeId
2)自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

两个重要的API

  1. GatewayFlowRule:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。
  2. ApiDefinition:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫 my_api,请求 path 模式为 /foo/** 和 /baz/** 的都归到 my_api 这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。

其中网关限流规则 GatewayFlowRule 的字段解释如下:

  1. resource:资源名称,可以是网关中的 route 名称或者用户自定义的API 分组名称。
  2. resourceMode:规则是针对 API Gateway 的route(RESOURCE_MODE_ROUTE_ID)还是用户在 Sentinel 中定义的API 分组(RESOURCE_MODE_CUSTOM_API_NAME),默认是route。
  3. grade:限流指标维度,同限流规则的grade 字段。
  4. count:限流阈值
  5. intervalSec:统计时间窗口,单位是秒,默认是1 秒(目前仅对参数限流生效)。
  6. controlBehavior:流量整形的控制效果,同限流规则的 controlBehavior 字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
  7. burst:应对突发请求时额外允许的请求数目(目前仅对参数限流生效)。
  8. maxQueueingTimeoutMs:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
  9. paramItem:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值