SpringCloud与微服务-第7章-API 网关服务 Spring Cloud Gateway

API 网关服务: Spring Cloud Gateway

API 网关是一个更为智能的应用服务器,它的定义类似于面向对象设计模式中的 Façade (门面) 模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤。它除了要实现请求路由、负载均衡、校验过滤等功能之外,还需要更多能力,比如于服务治理框架的结合、请求转发时的熔断机制、服务的聚合等一系列高级功能。

门面设计模式, 也叫外观模式, 是一种结构型设计模式, 它为子系统中的一组接口提供一个一致的界面, 此模式定义了一个高层接口, 这个接口使得这一子系统更加容易使用。比如 sl4j,它是一个日志门面,它的实现有 log4j,logback 等。


目标

在本章中,您将学习:

  • 介绍网关的作用和相关产品
  • GateWay 网关的快速入门
  • 路由详解
  • 过滤器详解
  • 动态路由
  • 动态过滤器

API 网关作用

对于整个微服务来说如果将每一个微服务的接口直接暴露给用户是错误的做法,这里主要体现出三个问题:

  1. 服务将所有 API 接口对外直接暴露给用户端,这本身就是不安全和不可控的,用户可能越权访问不属于它的功能,例如普通的用户去访问管理员的高级功能。

  2. 后台服务可能采用不同的通信方式,如服务 A 采用 RESTful 通信,服务 B 采用 RPC 通信,不同的接入方式让用户端接入困难。尤其是 App 端接入 RPC 过程更为复杂。

  3. 在服务访问前很难做到统一的前置处理,如服务访问前需要对用户进行鉴权,这就必须将鉴权代码分散到每个服务模块中,随着服务数量增加代码将难以维护。


bg fit


为了解决以上问题,API 网关应运而生,加入网关后应用架构变为下图所示。


bg fit


引入 API 网关后,在用户端与微服务之间建立了一道屏障,通过 API 网关为微服务访问提供了统一的访问入口,所有用户端的请求被 API 网关拦截并在此基础上可以实现额外功能,例如:

  • 针对所有请求进行统一鉴权、熔断、限流、日志等前置处理,让微服务专注自己的业务。

  • 统一调用风格,通常 API 网关对外提供 RESTful 风格 URL 接口。用户传入请求后,由 API 网关负责转换为后端服务需要的 RESTful、RPC、WebService 等方式,这样便大幅度简化用户的接入难度。

  • 更好的安全性,在通过 API 网关鉴权后,可以控制不同角色用户访问后端服务的权利,实现了服务更细粒度的权限控制。


API 网关是用户端访问 API 的唯一入口,从用户的角度来说只需关注 API 网关暴露哪些接口,至于后端服务的处理细节,用户是不需要知道的。从这方面讲,微服务架构通过引入 API 网关,将用户端与微服务的具体实现进行了解耦。


API 网关技术简介

以上便是 API 网关的作用,那 API 网关有哪些产品呢?

目前,市面上有两种网关组件比较流行。下面,我们将对这两种组件做一个简单的了解。


Spring Cloud Zuul

Spring Cloud Zuul 是 Spring Cloud Netflix 子项目的核心组件之一,可以作为微服务架构中的 API 网关使用,具有动态路由、过滤、压力测试、监控、弹性伸缩和安全等功能 ,为微服务架构中的服务提供了统一的访问入口。Zuul 和 Ribbon 以及 Eureka 相结合,可以实现智能路由和负载均衡的功能,可以将流量按照某种策略分发到集群中的多个实例。

zuul 本质上是一个 web servlet 应用,基于 JavaEE Servlet 技术栈,使用阻塞 API,处理的是 http 请求,没有提供异步支持,不支持任何长连接,比如 websocket。


bg fit


好景不长,后来 Netflix 内部产生分歧,Netflix 官方宣布 Zuul 停止维护,这让 Spring 机构也必须转型。于是 Spring Cloud 团队决定开发自己的第二代 API 网关产品:Spring Cloud Gateway。


Spring Cloud Gateway

与 Zuul 是“别人家的孩子”不同,Spring Cloud Gateway 是 Spring 自己开发的新一代 API 网关产品。它基于 NIO 异步处理,摒弃了 Zuul 基于 Servlet 同步通信的设计,因此拥有更好的性能。同时,Spring Cloud Gateway 对配置进行了进一步精简,比 Zuul 更加简单实用。


Spring Cloud Gateway 是基于 Spring 5.0 、Spring boot 2.0 和 Project Reactor,为微服务提供一个简单有效的网关 API 路由接口。

它作为 Spring Cloud 生态系统的网关,目标是为了代替 Zuul,Spring Cloud Gateway 是基于 webFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
Spring Cloud Gateway 提供统一的路由方式,基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。


技术对比

  1. Gateway 对比 Zuul 多依赖了 spring-webflux,内部实现了限流、负载均衡等,扩展性也更强。Zuul 内部没有实现限流、负载均衡等。

  2. Zuul 仅支持同步,Gateway 支持异步。

  3. Gateway 线程开销少,支持各种长连接、websocket,spring 官方支持,但运维复杂,Zuul 编程模型简单,开发调试运维简单,有线程数限制,延迟堵塞会耗尽线程连接资源。

  4. Zuul 1.x,是一个基于阻塞 io 的 API Gateway。Zuul 已经发布了 Zuul 2.x,基于 Netty,也是非阻塞的,支持长连接,但 Spring Cloud 暂时还没有整合计划。Spring Cloud 官方更加推荐的网关组件是 Gateway。


本章重点介绍 Spring Cloud Gateway 的使用方法、配置属性等。下面我们将通过一个快速入门案例,了解网关是如何在我们的项目中发挥作用的。


Spring Cloud Gateway 的关键特征:

  • 基于 JDK 8+ 开发;
  • 基于 Spring Framework 5 + Project Reactor + Spring Boot 2.0 构建;
  • 支持动态路由,能够匹配任何请求属性上的路由;
  • 支持基于 HTTP 请求的路由匹配(Path、Method、Header、Host 等);
  • 过滤器可以修改 HTTP 请求和 HTTP 响应(增加/修改 Header、增加/修改请求参数、改写请求 Path 等等)

当下 Spring Cloud Gateway 已然是 Spring Cloud 体系上 API 网关标准组件。Spring Cloud Gateway 十分优秀,Spring Cloud Alibaba 也默认选用该组件作为网关产品,下面我们就通过实例讲解 Spring Cloud Gateway 的使用办法。


GateWay 快速入门

介绍了一下关于网关服务的概念和作用,在这一节中,不妨用实际的示例来直观的体验一下 Spring Cloud Gateway 是如何使用和运作,并应用到微服务架构中去的。在搭建完网关基础服务之后,我们的整体项目架构将如图所示:


h:16em


构建网关

首先,在实现各种 API 网关服务的高级功能之前,我们需要做一些准备工作,比如,构建起最基本的 API 网关服务,并且搭建几个用于路由和过滤使用的微服务应用等。对于微服务应用,我们可以直接使用之前章节实现的 order-service 和 user-service 。


虽然之前我们一直将 order-service 视为消费者,但是在 Nacos 的服务注册与发现体系中,每个服务既是提供者也是消费者,所以 order-service 实质上也是一个服务提供者。之前我们访问的 http://localhost:8080/consumer/order/ 等一系列接口就是它提供的服务。读者也可以使用自己实现的微服务应用,因为这部分不是本章的重点, 任何微服务应用都可以被用来进行后续的试验。这里,详细介绍一下 API 网关服务的构建过程。


1.引入依赖

创建一个基础的 Spring Boot 工程,命名为 gateway-server,并在 pom.xml 中引入相关依赖,具体如下:


引入 Nacos 的服务发现依赖,可以结合 Spring Cloud Gateway 实现服务的拉取和负载均衡。对于 Nacos 的服务发现依赖,可以通过査看它的依赖内容了解到,它还包含了下面这些网关服务需要的重要依赖:

image-20220728114039071

  • spring-cloud-starter-hystrix :该依赖用来在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发 API 网关资源无法释放,从而影响其他应用的对外服务。
  • spring-cloud-starter-ribbon :该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。

2.创建主启动类


3.配置文件

创建 application.yml 配置文件,并进行如下配置:



完成上面的工作后,通过 Gateway 实现的 API 网关服务就构建完毕了。


4.测试

在上面的配置文件中,我们定义了两组路由规则,现在我们以其中的 order-service 为例,进行访问测试。 首先要启动 Nacos 服务,然后分别启动 gateway-server,order-service,user-service。

启动成功之后,访问: http://localhost:10010/order/101


image-20220728114819131

可以看到,我们访问的是网关路径,但实际上提供请求处理功能的是 order-service 服务。


路由请求

在入门案例中,我们在配置文件中进行了一些简单配置,就可以实现网关的路由功能,那么这些配置起到什么作用呢?


传统路由方式

在以往,我们发送 HTTP 请求至少需要知道服务端的 IP 和端口,这样我们就会固定的将请求路由到该服务端的具体 IP 和端口上。如下所示:



该配置定义了发往 API 网关服务的请求中,所有符合 http://GATEWAYTHOST:10010/user/** 规则的访问都将被路由转发到http://127.0.0.1:8081/user/** 地址上,也就是说,当访问 http://127.0.0.1:10010/user/** 的时候, API 网关服务会将该请求路由到 http://127.0.0.1:8081/user提供的微服务接口上。其中,配置属性 gateway.routes 下的 id 部分为路由的名字可以任意定义,下面将要介绍的面向服务的映射方式也是如此。


面向服务的路由(添加负载均衡功能)

前面讲的 uri 的配置方式,需要运维人员手动维护各个路由 path 与 url 的关系, 并且当某个服务的实例发生变化时,还需要手动修改路由配置,这样的方式显然不够友好。


为了解决这个问题, Spring Cloud Gateway 实现了与 Spring Cloud Nacos 和 Ribbon 的无缝整合:

  • 可以让路由的 path 不是映射具体的 url, 而是让它映射到某个具体的服务,而具体的 url 则交给 Nacos 的服务发现机制去自动维护,称这类路由为面向服务的路由。
  • 并且,面向服务的路由还可以实现负载均衡的功能,这样就可以让 API 网关服务在转发请求时,将请求均匀的分发到多个服务实例上,从而实现对微服务的负载均衡。

在 Gateway 中使用服务路由也同样简单,我们在入门案例中采用的就是面向服务的路由。


针对之前准备的两个微服务应用 order-service 和 user-service, 在上面的配置中分别定义了两个名为 order-service 和 user-service 的路由来映射它们。另外,通过指定 Nacos Server 服务注册中心的位置,除了将自己注册成服务之外,同时也让 Gateway 能够获取 order-service 和 user-service 服务的实例清单,以实现 path 映射服务,再从服务中挑选实例来进行请求转发的完整路由机制。


在完成了上面的服务路由配置之后,可以将 nacos-server,order-service 和 user-service 以及这里用 Spring Cloud Gateway 构建的 gateway-server 都启动起来,并且我们可以启动多个 order-service 和 user-service 的实例。启动完毕,在 nacos-server 的信息面板中,可以看到,除了 order-service 和 user-service 之外,多了一个网关服务 gateway,并且 order-service 和 user-service 的实例不止一个。


image-20220728210322861


通过面向服务的路由配置方式,不需要再为各个路由维护微服务应用的具体实例的位置,而是通过简单的 path 与 serviceld 的映射组合,使得维护工作变得非常简单。这完全归功于 Spring Cloud Alibaba Nacos 的服务发现机制,它使得 API 网关服务可以自动化完成服务实例清单的维护,完美地解决了对路由映射实例的维护问题。


开启动态路由

在上面的案例中,我们已经实现了基于 Nacos 服务发现机制的面向服务的路由配置,但是这种路由配置方式还是有一个问题,那就是当我们新增了一个微服务应用时,需要手动去配置一个新的路由,这样的话,就不是很符合微服务的敏捷开发理念了。


为了解决这个问题, Spring Cloud Gateway 提供了动态路由的功能,它可以让我们在不重启网关服务的情况下,实现路由的动态刷新。在 Spring Cloud Gateway 中,动态路由的配置信息是存储在内存中的,所以我们可以通过 Spring Cloud Gateway 提供的 API 来实现动态路由的刷新。


动态路由默认是关闭的, 如果需要开启, 修改配置:


在上面的配置中,我们通过 locator.enabled 属性来开启动态路由的功能,然后通过 lower-case-service-id 属性来统一使用小写的微服务 ID。这样,当我们新增了一个微服务应用时,只需要在 Nacos Server 中注册一个新的服务,然后在网关服务中调用 Spring Cloud Gateway 提供的 API,就可以实现动态路由的刷新。

比如, userservice 中有一个接口/user/{id}, 我们可以通过调用网关服务的动态路由进行调用 http://localhost:10010/userservice/user/1 , 非常方便。


动态路由也有一些限制, 比如需要调用的微服务必须能通过服务发现机制访问, 无法定制路由的规则 ,必须暴露所有的微服务接口等。

Gateway 也可以通过 Actuator API 接口实现动态路由的添加和删除, 这里不作介绍。


在 Spring Cloud Gateway 中,动态路由的刷新是通过 Spring Cloud Gateway 提供的 RouteLocator 接口来实现的。在 RouteLocator 接口中,有一个 refresh() 方法,它可以用来刷新路由信息。在 Spring Cloud Gateway 中,RouteLocator 接口的实现类有两个,一个是 PropertiesRouteLocator,另一个是 DiscoveryClientRouteLocator。其中, PropertiesRouteLocator 是通过配置文件来实现路由信息的加载,而 DiscoveryClientRouteLocator 是通过服务发现机制来实现路由信息的加载。


路由详解

在快速入门 一节的请求路由示例中,对 Spring Cloud Gateway 中的两类路由功能己经做了简单的使用介绍。在本节中,将进一步详细介绍关于 Spring Cloud Gateway 的路由功能,以方便用户更好地理解和使用它。


断言工厂

Spring Cloud Gateway 将路由作为 Spring WebFlux HandlerMapping 基础设施的一部分进行匹配。Spring Cloud Gateway 包括许多内置的路由断言工厂。所有这些断言都匹配 HTTP 请求的不同属性。您可以将多个路由断言工厂与逻辑和语句组合在一起。

在上一节快速入门中,同学们或许已经注意到配置文件有一个关键词:predicates ,这个就是路由断言工厂的配置标识。我们在此基础上进行路由配置。



我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件,例如 Path=/user/\*\*是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 类来处理的。

该配置的含义是,当请求路径以/user开头,那么 Gateway 网关就会把请求路由到 userservice 对应的实例上进行请求处理。如果请求路径不匹配该配置,则会寻找其他的配置项进行匹配。 假如最后所有的匹配项都不满足,网关将会响应一个包含 HTTP 404 状态码的响应信息。


PathRoutePredicateFactory 是我们在实际生产中最常用的一种断言工厂,除了 PathRoutePredicateFactory 之外,像这样的断言工厂在 SpringCloudGateway 还有十几个:


名称 说明 示例
After 是某个时间点后的请求 – After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点之前的请求 – Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之前的请求 – Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些 cookie – Cookie=chocolate, ch.p
Header 请求必须包含某些 header – Header=X-Request-Id, \d+
Host 请求 host(域名)必须包含 – Host= .somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 – Method=GET,POST
Path 请求路径必须符合指定规则 – Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 – Query=name, Jack 或者 – Query=name
RemoteAddr 请求者的 ip 必须是指定范围 – RemoteAddr=192.168.1.1/24

在 Spring Cloud 的官方文档上,对于断言工厂的用法有详细的介绍,具体可以参考如下网址: https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories


现在,我们以 After 为例,对入门案例进行改造。在配置文件中,我们做如下改动:



我们在配置文件中添加了一条时间信息的配置,该配置表示访问/order/**的请求,同时还要满足另一个条件,那就是该请求的当前时间必须在规定的日期之后。只有同时满足路径信息和时间信息的规则,请求才会被路由到 orderservice 进行处理。


像入门案例一样,做好启动服务的准备工作之后,我们访问:http://localhost:10010/order/101, 如果在整个配置中找不到合适的路由匹配,那将如下所示:

image-20220729124406464


然后我们将 After 所指定的时间改为一个可以被断言通过的时间,比如:


重启网关服务,再次访问:http://localhost:10010/order/101,可以看到如下结果:

image-20220729125454080

由此可以看出,After 所配置的规则,可以控制请求访问是否被允许。目前最新版本的断言工厂一共有十二种,用法大致类似,具体的使用案例我们可以在官方文档中轻松找到,此处不再一一赘述。


过滤器详解

在本章一开始的技术简介中,我们了解到 Spring Cloud Gateway 基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。在本节中,将对 Gateway 的请求过滤器功能做进一步的介绍和总结。


路由过滤器

GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。在快速入门小节,当我们的请求被网关接受到之后,该请求就会立刻被路由到相应的微服务端吗? 其实并不是的,在请求被真正路由到微服务之前,其实还会经理一系列的过滤器的过滤,最后才会到达微服务。


bg right fit

客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关 Web 处理程序。

此处理程序通过特定于请求的过滤器链运行请求。过滤器用虚线划分的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“预”过滤器逻辑。然后发出代理请求。发出代理请求后,将运行“发布”过滤器逻辑。


路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。路由过滤器的范围是特定的路由。Spring Cloud Gateway 包含许多内置的 GatewayFilter 工厂。

目前最新版本的 Spring Cloud Gateway 的官方文档,一共有 33 种路由过滤器,以后可能还有更多路由过滤器加入进来。同学们可以访问该文档: https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories 进行查看和学习。


如此之多的过滤器,我们不必每一个都进行学习。我们学会其中比较常用的一些过滤器的用法即可,至于其他的过滤器,往往在我们具体使用到它们的时候再去查看具体的官方文档。下面我们列举部分的路由过滤器:

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量

AddRequestHeader

从名字上可以轻易看出来这个过滤器的功能,顾名思义是给当前请求添加一个请求头。现在,我们完成下面这个需求:


完成这个需求非常简单,我们只需要修改 gateway 服务的 application.yml 文件,添加路由过滤即可:


当前过滤器写在 userservice 路由下,因此仅仅对访问 userservice 的请求有效,而对于访问 orderservice 的请求则无效。AddRequestHeader 的值由两部分组成,并以英文逗号分隔。逗号左边是请求头的 name,而右边则是该请求头所对应的具体的信息。

现在,我们在 userservice 中使用 Spring MVC 的@RequestHeader 注解,接收 Description 请求头的值。



重启 Gateway 和 userservice 之后,我们访问:http://localhost:10010/user/1,可以在 IDEA 的控制台看到如下信息:

image-20220729172346325


其余三十余种路由过滤器,其用法与上述例子非常类似,因此我们不再进行一一赘述。具体的使用方式,可以进行查阅官方文档进行学习使用。


默认过滤器

上述例子是只针对某个具体的微服务生效的路由过滤器的配置方式,但是我们如果想要对所有的微服务添加同样的过滤器,那应该怎么办呢? 最容易想到的办法就是在每组微服务的路由下面加上相同的配置。这样做虽然可以实现需求,但是缺点也很明显,不但工作量大,而且容易出错,在后续的维护中也会增加更多的工作量。


Spring Cloud Gateway 的默认过滤器,正是为了解决这个问题而设计的。如果要对所有的路由都生效,则可以将过滤器工厂写到 default 下。格式如下:

注意:default-filters 的层级与 routes 层级相同,可以对所有的路由都生效。


全局过滤器

上一节学习的路由过滤器,Spring Cloud Gateway 官方文档目前一共提供了 33 种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。那么如果我们想自定义过滤器的实现逻辑,有什么办法呢? 我们可以实现 GlobalFilter 接口,编写自己的业务逻辑。


全局过滤器作用

全局过滤器的作用与 GatewayFilter 的作用一样,也是处理一切进入网关的请求和微服务响应。区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的,而 GlobalFilter 的逻辑需要自己写代码实现。



我们可以实现 GlobalFilter 接口,在 filter 函数中编写自定义逻辑,实现下列功能:

  • 登录状态判断
  • 权限校验
  • 请求限流等

自定义全局过滤器

我们可以使用全局过滤器实现自定义业务逻辑,现在就让我们完成一个小案例感受一下吧。首先,明确一下我们的需求:

接下来,我们就动手实践一下吧。

创建一个类,实现 GlobalFilter 接口:



编写完成之后,我们重启网关,重新访问:http://localhost:10010/order/101,结果如图所示:

image-20220730100835237


由于请求中没有 auth=niit 的这样一个参数,无法通过自定义过滤器的逻辑校验。我们给请求中添加参数,访问:http://localhost:10010/order/101?auth=niit ,结果如图所示,可以再次正常访问:

image-20220730101200580


通过这个简单的案例,我们学会了如何自定义实现过滤器逻辑。当然,我们这里的案例是最简单的一种使用,在实际生产中肯定会有更为复杂的需求,但是基本原理我们掌握了,那么复杂的需求我们只需一步一步拆分实现即可。


过滤器链执行顺序

在上一节的自定义实现过滤器小案例中,细心的同学可能已经注意到我们在编写实现代码的时候,MyAuthFilter 类上除了@Component 这个注解外,还有一个注解@Order(-1)。 这个注解有什么用呢? order,顾名思义是顺序的意思,这个注解的作用正是为了给过滤器进行排序的。下面我们来探究一下过滤器的执行顺序。


我们现在一共接触了三大类过滤器: 当前路由的过滤器(三十多种)、DefaultFilter(默认过滤器)、GlobalFilter(全局过滤器)。网关接受请求并确定路由后,会将当前路由过滤器和 DefaultFilterGlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器,在默认情况下,三类过滤器的执行顺序如下:

h:10em


我们知道,每一类的过滤器,在整个的过滤器链上可能都不止一个,那么它们具体的排序规则又是什么呢? 我们先从最容易理解的全局过滤器开始。

我们可以在自定义的全局过滤器上添加@Order 注解进行排序,这个很好理解。



通过源码注释,我们得知如下结论:

  • @Order 注解的值越小,优先级越高,默认是最低优先级。

我们可以给自定义全局过滤器添加@Order 注解进行排序,那么默认过滤器和路由过滤器的排序顺序又是怎么样的呢?目前,我们只能对其进行配置,而无法手动添加 order 的值,这两类过滤器的顺序由 Spring 框架帮我们指定。每一类的过滤器的 order 值,默认是按照在配置文件中的声明顺序从 1 递增。


现在我们对 Gateway 服务的配置文件进行改动,增加一些过滤器,然后我们通过 IDEA 的断点调试功能来观察它们的执行顺序:



配置文件做如上改动之后,我们在 AddRequestParameterGatewayFilterFactory 类的 filter 函数处打上断点,以 Debug 模式启动 Gateway 并重新访问:http://localhost:10010/order/101?auth=niit


image-20220730120155887


仔细观察之后,最终可以得出如下结论:

  • 路由过滤器和 defaultFilterorder 值由 Spring 指定,默认是按照声明顺序从 1 递增。
  • 当这两类过滤器的 order 值一样时,会按照 defaultFilter -> 路由过滤器的顺序执行。
  • 自定义全局过滤器总是在这两类过滤器执行之后再执行,多个自定义全局过滤器的执行顺序由其@Order 注解的值进行排序,@Order 注解的值越小,越先执行。

跨域配置

在实际生产中,网关不可避免的会接收到跨域 AJAX 请求,如果不做处理,浏览器会默认将跨域 AJAX 请求进行拦截。我们之前在学习 Servlet 的时候已经知道可以使用 CORS 的解决方案进行解决,而 Gateway 是基于 WebFlux 构建的网关服务,此前针对与 Servlet 体系所学习的方法不一定适用。但是我们即使没有学过 WebFlux 也不用担心,Gateway 底层已经将跨域问题的处理逻辑实现好了,我们只用进行简单配置即可。


跨域问题

跨域: 协议://域名:端口任何一个出现不一致都会导致跨域,主要包括:

  • 协议不同, 例如 http://xxxhttps://xxx
  • 域名不同: 例如 www.baidu.comwww.taobao.org
  • 域名相同,端口不同:localhost:8080localhost:8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域 ajax 请求,当出现跨域请求时浏览器会拦截该请求的问题。

因为跨域导致的报错示例: Access to XMLHttpRequest at 'http://localhost:10010/order/101?auth=niit' from origin 'http://localhost:8090' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.


解决方案

修改 Gateway 服务的 application.yml 文件:



Gateway 实现灰度发布

灰度发布是指在黑与白之间的过渡阶段,我们可以将灰度发布理解为一种渐进式发布,即将新版本的服务逐步引入到生产环境中,而不是一次性将新版本的服务全部引入到生产环境中。灰度发布的好处在于,当新版本的服务出现问题时,我们可以快速的回滚到旧版本的服务,而不会影响到整个系统的稳定性。


灰度发布的实现

灰度发布的实现方式有很多种,例如: 通过 Nginx 的反向代理实现灰度发布、通过 DNS 的负载均衡实现灰度发布、通过服务网关实现灰度发布等。而我们在这里主要介绍通过服务网关实现灰度发布的方式。


灰度发布的实现原理

灰度发布的实现原理是:通过服务网关对请求进行拦截,然后根据请求的特征信息(例如:请求的 IP 地址、请求的参数、请求的 Header 等)将请求转发到不同的服务实例中。例如: 发布新版本时, 谨慎起见, 为了提高发布质量, 降低风险, 减少全量发布带来的服务中断影响, 在老版本代码持续提供的基础上, 只切换一小部分流量到部署新版本代码的机器上测试, 来实现灰度发布.


灰度发布的实现步骤

分别在 Nacos 上运行老代码账户微服务(使用端口 3333), 新代码账户微服务,(使用端口 5555), 网关项目(使用端口 8080)


网关项目的配置如下:


这里是通过分组权重的设置实现流量控制的, 其中组 accountGroup 总权重为 10, 其中 oldVersion_Route 占 9, newVersion_Route 占 1.

测试:

因此平均每 10 次请求 getAccount, 会有一次使用新 Api.


Gateway 添加 Hystrix 支持

网关项目添加 hystrix 依赖 spring-cloud-starter-netflix-hystrix 依赖,开启 hystrix 支持:


配置实例


  • 首先 filter 里头配置了 name 为Hystrix的 filter,实际是对应HystrixGatewayFilterFactory
  • 然后指定了 hystrix filter 的名称name,及fallbackUri,注意 fallbackUri 要以forward开头, 表示如果发生降级则转发到指定的 uri 上
  • 最后通过hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds指定该 command 的超时时间

RewritePath filter 的作用是将请求的路径重写,这里是将/employee/xxx重写为/xxx,这样就可以将请求转发到 employee-service 服务的/xxx路径上.


fallback 示例:


总结:

spring cloud gateway 集成 hystrix,分为如下几步:

  1. 添加 spring-cloud-starter-netflix-hystrix 依赖
  2. 在对应 route 的 filter 添加 name 为 Hystrix 的 filter,同时指定 hystrix command 的名称,及其 fallbackUri(可选)
  3. 指定该 hystrix command 的超时时间等。

Hystrix GatewayFilter Factory – HystrixGatewayFilterFactory


活动 7.1

使用 Gateway 网关+Nacos 实现微服务统一调用


练习问题


  1. Spring Cloud Gateway 不支持异步。
    a. 错误
    b. 正确

    查看答案

    a


  1. Spring Cloud Gateway 内置了许多 Route Predicate 工厂。这些 PredicateFactory 都与 HTTP 请求的不同属性匹配。多个 RoutePredicateFactory 可以进行组合。
    a. 错误
    b. 正确

    查看答案

    b


  1. Spring Cloud Gateway 中不包含以下哪类过滤器?

    a.路由过滤器
    b.服务过滤器
    c.默认过滤器
    d.全局过滤器

    查看答案

    b


  1. 浏览器禁止请求的发起者与服务端发生跨域 ajax 请求,当出现跨域请求时浏览器不会拦截该请求。

    a. 错误
    b. 正确

    查看答案

    a


小结

在本章中,您学习了:

  • 使用 Spring Cloud Gateway 构建起最基本的 API 网关服务。
  • 为网关服务添加请求路由的功能。
  • 添加相应的请求过滤。
  • 断言工厂
  • 过滤器
  • 路由过滤器
  • 默认过滤器
  • 全局过滤器
  • 跨域问题处理

Views: 34