SpringCloud学习——服务网关Gateway
在微服务架构中,一个系统往往由多个微服务组成,而这些服务可能部署在不同机房、不同地区、不同域名下。这种情况下,客户端(例如浏览器、手机、软件工具等)想要直接请求这些服务,就需要知道它们具体的地址信息,例如 IP 地址、端口号等。
这种客户端直接请求服务的方式存在以下问题:
- 当服务数量众多时,客户端需要维护大量的服务地址,这对于客户端来说,是非常繁琐复杂的。
- 在某些场景下可能会存在跨域请求的问题。
- 身份认证的难度大,每个微服务需要独立认证。
API 网关是一个搭建在客户端和微服务之间的服务,我们可以在 API 网关中处理一些非业务功能的逻辑,例如权限验证、监控、缓存、请求路由等。
API 网关就像整个微服务系统的门面一样,是系统对外的唯一入口。有了它,客户端会先将请求发送到 API 网关,然后由 API 网关根据请求的标识信息将请求转发到微服务实例。
对于服务数量众多、复杂度较高、规模比较大的系统来说,使用 API 网关具有以下好处:
- 客户端通过 API 网关与微服务交互时,客户端只需要知道 API 网关地址即可,而不需要维护大量的服务地址,简化了客户端的开发。
- 客户端直接与 API 网关通信,能够减少客户端与各个服务的交互次数。
- 客户端与后端的服务耦合度降低。
- 节省流量,提高性能,提升用户体验。
- API 网关还提供了安全、流控、过滤、缓存、计费以及监控等 API 管理功能。
SpringCloud Gateway 基本概念
SpringCloud Gateway 是在建立在 Spring 生态系统之上的 API 网关服务,基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式,以及提供一些强大的过滤器功能,例如:安全,监控/指标、熔断、限流、重试等。
SpringCloud Gateway 的目标是替代 Zuul,在 Spring Cloud 2.0 以上版本中,没有对 Zuul 2.x 高性能版本进行集成,仍然还是使用的 Zuul 1.x 的非 Reactor 模式的老版本。而为了提升网关的性能,SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
IO 模型
Zuul 1.x 的 IO 模型
SpringCloud 中所集成的 Zuul 1.x 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet IO 处理模型。servlet 由 servlet container 进行生命周期管理:
- container 启动时构造 servlet 对象并调用
servlet.init()
进行初始化; - container 关闭时调用
servlet.destory()
销毁 servlet; - container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用
service()
。
上述模式的缺点:servlet 是一个简单的网络 IO 模型,当请求进入 servlet container 时,servlet container 就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发,线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单的业务场景下,不希望为每个请求分配一个线程,只需要一个或几个线程就能应对极大并发的请求,这种业务场景下 servlet 模型没有优势。
所以 Zuul 1.x 是基于 servlet 阻塞 IO 模型的 API 网关,即 Spring 实现了处理所有 request 请求的一个 servlet(DispatcherServlet),并由该 servlet 阻塞式处理处理。每次 I/О 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成。所以 Zuul 1.x 无法摆脱 servlet 模型的弊端。
虽然 Zuul 2.0 开始,使用了 Netty 非阻塞和支持长连接,并且已经有了大规模 Zuul 2.0 集群部署的成熟案例,但是,SpringCloud 官方已经没有集成改版本的计划了。
GateWay 非阻塞异步模型
传统的Web框架,比如说:Struts2,SpringMVC 等都是基于 Servlet APl 与 Servlet 容器基础之上运行的。
但是在 Servlet3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖 Servlet APl,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
Spring Cloud Gateway 路由转发
Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念:
核心概念 | 描述 |
---|---|
Route(路由) | 网关最基本的模块。它由一个 ID、一个目标 URI、一系列的断言(Predicate)和过滤器(Filter)组成。 |
Predicate(断言) | 路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务。 |
Filter(过滤器) | 过滤器,可以在请求被路由前或者之后对请求进行拦截修改。 |
注意:其中 Route 和 Predicate 必须同时声明。
Gateway 的工作流程
Spring Cloud Gateway 工作流程说明如下:
- 客户端将请求发送到 Spring Cloud Gateway 上。
- Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler。
- Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑,返回响应结果返回给客户端。
- 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
- 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
- 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。
总而言之,客户端发送到 Spring Cloud Gateway 的请求需要通过一定的匹配条件,才能定位到真正的服务节点。在将请求转发到服务进行处理的过程前后(pre 和 post),我们还可以对请求和响应进行一些精细化控制。
Predicate 就是路由的匹配条件,而 Filter 就是对请求和响应进行精细化控制的工具。有了这两个元素,再加上目标 URI,就可以实现一个具体的路由了。
服务搭建
1 | <dependency> |
1 | server: |
1 |
|
目标:给 8001 服务套上网关
网关路由的两种配置方式
YML 配置
1 | spring: |
代码配置
当请求路径为 /guonei
时,转发到 https://news.baidu.com/guonei
。
1 |
|
Gateway 动态路由
默认情况下,Spring Cloud Gateway 会根据服务注册中心(例如 Eureka Server)中维护的服务列表,以服务名(spring.application.name)作为路径创建动态路由进行转发,从而实现动态路由功能。
字段 | Route URL 说明 |
---|---|
lb | 即指通过注册中心的服务名称来转发请求 |
ws | 代表这是一个websocket请求,会通过长连接的方式转发请求 |
http | 转发http请求,可以直接链接到一个web url |
我们可以在配置文件中,将 Route 的 uri 地址修改为以下形式。
1 | lb://service-name |
- lb:uri 的协议,表示开启 Spring Cloud Gateway 的负载均衡功能。
- service-name:服务名,Spring Cloud Gateway 会根据它获取到具体的微服务地址。
1 | spring: |
Predicate 断言
Spring Cloud Gateway 通过 Predicate 断言来实现 Route 路由的匹配规则。简单点说,Predicate 是路由转发的判断条件,请求只有满足了 Predicate 的条件,才会被转发到指定的服务上进行处理。
使用 Predicate 断言需要注意以下 3 点:
- Route 路由与 Predicate 断言的对应关系为“一对多”,一个路由可以包含多个不同断言。
- 一个请求想要转发到指定的路由上,就必须同时匹配路由上的所有断言。
- 当一个请求同时满足多个路由的断言条件时,请求只会被首个成功匹配的路由转发。
常用的 Predicate
常见的 Predicate 断言如下表(假设转发的 URI 为 http://localhost:8001)。
断言 | 示例 | 说明 |
---|---|---|
Path | - Path=/dept/list/** | 当请求路径与 /dept/list/** 匹配时,该请求才能被转发到 http://localhost:8001 上。 |
Before | - Before=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 在某个时间之前的请求,才会被转发。 |
After | - After=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 在某个时间之后的请求,才会被转发。 |
Between | - Between=2021-10-20T15:18:33.226+08:00[Asia/Shanghai],2021-10-20T15:23:33.226+08:00[Asia/Shanghai] | 在某个时间段内的请求,才会被转发。 |
Cookie | - Cookie=name,kimtou | 携带 Cookie 且 Cookie 的内容为 name=kimtou 的请求,才会被转发。 |
Header | - Header=X-Request-Id,\d+ | 请求头上携带属性 X-Request-Id 且属性值为整数的请求,才会被转发。 |
Host | - Host=www.baidu.com | 当主机名为www.baidu.com的时候直接转发 |
Method | - Method=GET | 只有 GET 请求才会被转发。 |
示例
1 | - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议与服务名对应 |
CMD 测试:
1 | curl http://localhost:9527/payment/lb --cookie "name=kimtou" |
Filter 过滤器
通常情况下,出于安全方面的考虑,服务端提供的服务往往都会有一定的校验逻辑,例如用户登陆状态校验、签名校验、token 校验等。
在微服务架构中,系统由多个微服务组成,所有这些服务都需要这些校验逻辑,此时我们就可以将这些校验逻辑写到 Spring Cloud Gateway 的 Filter 过滤器中。
Filter 的分类
Spring Cloud Gateway 提供了以下两种类型的过滤器,可以对请求和响应进行精细化控制。
过滤器类型 | 说明 |
---|---|
Pre 类型 | 这种过滤器在请求被转发到微服务之前可以对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。 |
Post 类型 | 这种过滤器在微服务对请求做出响应后可以对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。 |
按照作用范围划分,Spring Cloud gateway 的 Filter 可以分为 2 类:
- GatewayFilter:应用在单个路由或者一组路由上的过滤器。
- GlobalFilter:应用在所有的路由上的过滤器。
GatewayFilter 网关过滤器
GatewayFilter 是 Spring Cloud Gateway 网关中提供的一种应用在单个或一组路由上的过滤器。它可以对单个路由或者一组路由上传入的请求和传出响应进行拦截,并实现一些与业务无关的功能,比如登陆状态校验、签名校验、权限校验、日志输出、流量监控等。
1 | - id: xxxx |
GlobalFilter 全局过滤器
GlobalFilter 是一种作用于所有的路由上的全局过滤器,通过它,我们可以实现一些统一化的业务功能,例如权限认证、IP 访问限制等。当某个请求被路由匹配时,那么所有的 GlobalFilter 会和该路由自身配置的 GatewayFilter 组合成一个过滤器链。
Spring Cloud Gateway 为我们提供了多种默认的 GlobalFilter,例如与转发、路由、负载均衡等相关的全局过滤器。但在实际的项目开发中,通常我们都会自定义一些自己的 GlobalFilter 全局过滤器以满足我们自身的业务需求,而很少直接使用 Spring Cloud Config 提供这些默认的 GlobalFilter。
1 |
|