Spring Cloud Gateway服务网关
一、 概述
Gateway官网 :https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
1.1 Gateway是什么
- Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring 5,Spring Boot 2 和Project Reactor 等技术。
- Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能例如︰熔断、限流、重试等。
- SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul。为了提升网关的性能 SpringCloud Gateway 是基于 WebFlux 框架实现的,Webflux 中的 reactor-netty 响应式编程组件,其底层使用了 Netty 通讯框架。
- Spring Cloud Gateway 的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控 / 指标和限流。
- Gateway 组件
1.2 Gateway网关能做什么
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
……
架构简图
1.3 我们为什么选择Gatway
Neflix 不太靠谱,zuul2.0 一直延期,迟迟不发布。
1.4 SpringCloud Gateway 具有如下特性
- 基于Spring Framework 5, Project Reactor和Spring Boot 2.0进行构建;
- 动态路由:能够匹配任何请求属性;
- 可以对路由指定 Predicate (断言)和Filter (过滤器);
- 集成Hystrix的断路器功能;
- 集成 Spring Cloud服务发现功能;
- 易于编写的Predicate (断言)和Filter (过滤器);
- 请求限流功能;
- 支持路径重写。
1.5 SpringCloud Gateway与Zuul的区别
- zuul 1.x,是一个基于阻塞 IO 的 API Gateway
- Zuul 1.x 基于Servlet 2.5使用阻塞架构它不支持任何长连接(如
WebSocket)Zuul的设计模式和Nginx较像,每次VО操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。 - Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2x 的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway 的 RPS (每秒请求数)是 Zuul 的1.6倍。
- Spring Cloud Gateway 建立在 Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
- Spring Cloud Gateway还支持 WebSocket,并且与Spring紧密集成拥有更好的开发体验。
1.6 Zuul 1 模型
Springcloud 中所集成的 Zuul 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet lO 处理模型。
Servlet的生命周期
- servlet 由 servlet container 进行生命周期管理。
- container 启动时构造 servlet 对象并调用 servlet init() 进行初始化;
- container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用 service()。
- container 关闭时调用 servlet destory() 销毁servlet;
上述模式的缺点: - servlet 是一个简单的网络 IO 模型,当请求进入 servlet container 时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发,线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。
- 在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势。
- 所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet (DispatcherServiet)并由该serviet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端。
在这里插入图片描述
1.7 WebFlux是什么?
- WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关API实现的。相对于传统的 web 框架来说,它可以运行在诸如 Netty , Undertow 及支持Servlet3.1 的容器上。非阻塞式+函数式编程 (Spring5必须让你使用java8)
- Spring WebFlux 是 Spring 5.0引入的新的响应式框架,区别于 Spring MVC ,它不需要依赖 Servlet API ,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
二、 三大核心概念
2.1 Route (路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
2.2 Predicate(断言)
参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
2.3 Filter (过滤)
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
2.4 总体
Web 请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精简细化控制。
Predicate 就是匹配条件,Filter 可以理解为一个无所不能的拦截器,再加上 URI,就可以实现一个具体的路由了。
三、Gateway 工作流程
- 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到GatewayWeb Handler。
- Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
- 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
- Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑 :路由转发+执行过滤器链
四、 Spring Cloud Gateway服务网关搭建
4.1 路由网关配置方式一 :
- 新建模块 :cloud-gateway-9527
- 修改 POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xu-parent</artifactId>
<groupId>com.xu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-9527</artifactId>
<dependencies>
<!--新增gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.xu</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 编写 YML
server:
port: 9527
spring:
application:
name: @artifactId@
cloud:
gateway:
# 开启从注册中心动态创建路由的功能,利用微服务名进行路由
discovery:
locator:
enabled: true
routes:
# 路由 1
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service # 动态路由,修改为服务名称
predicates:
- Path=/provider/getPaymentById/** #断言,路径相匹配的进行路由
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Cookie=username,zzyy # 表示名为 username 的 Cookie 的值必须符合后面的规则才可以
- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.demo.com
- Method=GET
- Query=username, \d+ #要有参数名称并且是正整数才能路由
# 路由 2
- id: payment_routh2
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/provider/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
- 编写主启动类
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class, args);
}
}
- 启动测试
启动 Eureka-7001、Provider-8001、Gateway-9527
访问 :http://localhost:7001/
访问 :http://localhost:8001/provider/getPaymentById/1
访问 :http://localhost:9527/provider/getPaymentById/1
4.2 路由网关配置方式二 :
官网的代码示例:
下面来自己实现,需求:通过 9527 网关,连接百度新闻网
- 新建一个 Config 包
- 新建一个配置类 :GatewayConfig
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_rote", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
4.3 动态路由
- 默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
- 把服务调用地址改成服务名称
五、Predicate
5.1 概述
启动 Gateway 模块后,控制台会有这样一段输出
Route Predicate Factories 是什么?
- Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
- Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate工厂可以进行组合 。
- Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory 创建
Predicate对象,Predicate对象可以赋值给Route。Spring Cloud Gateway包含许多内置的Route Predicate Factories。 - 所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。
5.2 常用的Route Predicate
1.After
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
2.Before
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
3.Between
4.Cookie
- Cookie=username,zzyy # 表示名为 username 的 Cookie 的值必须符合后面的规则才可以
使用 CURL 进行测试
加入curl返回中文乱码解决 :https://blog.csdn.net/leedee/article/details/82685636
5.Header
- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
6.Host
- Host=**.demo.com
7.Method
- Method=GET
8.Path
9.Query
- Query=username, \d+ #要有参数名称并且是正整数才能路由
六、Filter
6.1 概述
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂 类来产生。
6.2 Spring Cloud Gateway的Filter
1.生命周期
pre —— 在业务逻辑之前
post —— 在业务逻辑之后
2.种类
- GatewayFilter —— 单一Filter
- GlobalFilter —— 全局
6.3 自定义全局GlobalFilter
- 需要实现两个接口 :GlobalFilter ,Ordered
1.新建一个 filter 包
2.新建一个类 :MyLogGateWayFilter
@Slf4j
@Component
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(StringUtils.isEmpty(uname)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/** 加载过滤器顺序,返回值越小,优先级越高 */
@Override
public int getOrder() {
return 0;
}
}
3.测试
启动项目
访问 :http://localhost:9527/provider/payment/lb?uname=z3
转载:https://blog.csdn.net/weixin_42548384/article/details/116987084