目录
一、GateWay 核心简介:
(1)SpringCloud Gateway官网地址:https://spring.io/projects/spring-cloud-gateway
(2)SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
提前声明:Spring Cloud Gateway 底层使用了高性能的通信框架Netty。
(3)Spring Cloud Gateway 特性(/能干嘛):
- 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0
- 能够匹配任何请求属性的路由。
- 断言和过滤器特定于路由。
- 断路器集成。
- Spring Cloud DiscoveryClient 集成
- 易于编写断言和过滤器
- 请求速率限制
- 路径重写
(4)网关在架构中的位置,可以看到是请求进来由网关路由分配找到需要请求的服务,其中Nginx是用来做网关高可用的:
我们为何选择/要用Gateway(三点):
-
netflix不太靠谱,zuul 2.0一直跳票,迟迟不发布
一方面因为Zuul 1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。
Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的Zuul 2.x,但SpringCloud貌似没有整合计划。而且Netflix相关组件都宣布进入维护期。多方面综合考虑Gateway是很理想的网关选择。 -
SpringCloud Gateway具有如下特性:
基于Spring Framework 5,Project Reactor和Spring Boot 2.0构建
动态路由:能够匹配任何请求属性
可以对路由指定 Predicate(断言)和Filter(过滤器)
集成Hystrix的断路器功能
集成Spring Cloud 的服务发现功能
易于编写的Predicate(断言)和Filter(过滤器)
请求限流功能
支持路径重写 -
SpringCloud Gateway 与 zuul 的区别:
在SpringCloud Finchley 正式版之前,SpringCloud推荐的网关是Netflix提供的Zuul:
(1)Zuul 1.x是一个基于阻塞 I/O 的API Gateway
(2)Zuul 1.x基于servlet 2.5使用阻塞架构它不支持任何长连接(如websocket)Zuul的设计模式和Nginx较像,每次I/O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有一次加载较慢的情况,使得zuul的性能相对较差
(3)Zuul 2.x理念更先进,向基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,SpringCloud Gateway的RPS(每秒请求数)是Zuul的1.6倍
(4)SpringCloud Gateway建立在Spring Framework5、Project Reactor和Spring Boot 2之上,使用非阻塞API
(5)SpringCloud Gateway还支持WebSocket,并且与Spring紧密集成用于更好的开发体验
zuul 1.x模型:
SpringCloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
Servlet生命周期?
servlet 由 servlet container 进行生命周期管理
container 启动时构造 servlet 对象并调用 servlet init() 进行初始化;
container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service();
container 关闭时调用 servlet destory() 销毁servlet;
上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(比如用jmeter压测),线程数量就会涨,而线程资源代价是昂贵的(上下文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势
所以Zuul 1.x是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理。所以SpringCloud Zuul无法摆脱servlet模型的弊端。
Gateway模型:
Gateway支持 Reactor 和 WebFlux:
传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与servlet容器基础之上运行的。
但是Servlet3.1之后有了异步非阻塞的支持,而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对与传统的Web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程
Spring WebFlux 是 Spring 5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范
二、GateWay 三大核心概念:
三大核心概念:
(1)Route(路由):: ⽹关最基础的部分,也是⽹关⽐较基础的⼯作单元。路由由⼀个ID、⼀个⽬标URL(最终路由到的地址)、⼀系列的断⾔(匹配条件判断)和Filter过滤器(精细化控制)组成。如果断⾔为true,则匹配该路由。
(2)Predicate(断言)::参考了Java8中的断⾔java.util.function.Predicate,开发⼈员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于nginx中的location匹配⼀样),如果断⾔与请求相匹配则路由。
(3)Filter(过滤器)::⼀个标准的Spring webFilter,使⽤过滤器,可以在请求之前
或者之后执⾏业务逻辑。
Predicates断⾔就是我们的匹配条件,⽽Filter就可以理解为⼀个⽆所不能的拦截器,有了这两个元素,结合⽬标URL,就可以实现⼀个具体的路由转发。
Spring Cloud GateWay 帮我们内置了很多 Predicates功能,实现了各种路由匹配规则(通过 Header、请求参数等作为条件)匹配到对应的路由。
漏由的yml一般格式:
spring:
cloud:
gateway:
routes: # 路由可以有多个
- id: service-xxx-router # 我们⾃定义的路由 ID,保持唯⼀
uri: lb://server-name
predicates: #路由条件
- Path=/xx/xxxx/**
三、GateWay 工作流程:
工作流程图的总结:
(1)客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配的路由,将其发送到GateWay Web Handler;
(2)Handler再通过指定的过滤器链来将请求发送到我们实际的服务执⾏业务逻辑,然后返回。过滤器之间⽤虚线分开是因为过滤器可能会在发送代理请求之前(pre)或者之后(post)执⾏业务逻辑。
(3)Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、⽇志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改、⽇志的输出、流量监控等有非常重要的。
过滤器的分类:
从过滤器⽣命周期(影响时机点)的⻆度来说,主要有两个pre和post:
从过滤器类型的⻆度 ,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种:
一般情况下自定义GlobalFilter全局过滤器是程序员使⽤⽐较多的过滤器;
可以用来自定义一些黑名单校验等。
四、GateWay 入门配置:
(1)建模块:cloud-gateway-gateway9527
(2)pom:注意网关的依赖不需要web相关的依赖,包括web和web监控的依赖
<dependencies>
<!-- GateWay不需要使⽤web模块,它引⼊的是WebFlux(类似于SpringMVC) -->
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--引⼊webflux-->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>-->
<!--eureka 服务-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency><!--热部署-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.11</version>
</dependency>
</dependencies>
(3)yml:
server:
port: 9527 #服务端口号,建议一定有
spring:
application:
name: cloud-gateway #微服务名称,建议有
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #是否抓取注册信息。
service-url:
defaultZone: http://eureka7003.com:7003/eureka #单机版只写自己本身
# 集群版 写注册中心所有的 机器地址 (有2个注册中心集群 就写两个地址)
#defaultZone: http://eureka7003.com:7003/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
(4)主启动类:com.fan.springcloud.GatewayMain9527
package com.fan.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class, args);
}
}
怎么做到漏由:
这里附上提供者8001的controller:
package com.fan.springcloud.controller;
import com.fan.springcloud.entity.CommonResult;
import com.fan.springcloud.entity.Payment;
import com.fan.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")//读取到8001端口号
private String serverPort;
@Resource
private PaymentService paymentService;
@PostMapping("/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("插入的结果是{}",result);
if(result > 0){
return new CommonResult(200,"插入成功,返回结果"+result+"\t"+"服务端口:"+serverPort,payment);
}else {
return new CommonResult(444,"插入数据失败",null);
}
}
@GetMapping("payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("服务端接口————》插入的结果{}",payment);
if(payment != null){
return new CommonResult(200,"查询成功,服务的端口serverPort:"+serverPort,payment);
}else {
return new CommonResult(444,"没有对应的记录,查询id"+id,null);
}
}
@GetMapping("payment/lb")//测试负载均衡的
public String getPort(){
return serverPort;
}
}
修改yml:
server:
port: 9527 #服务端口号,建议一定有
spring:
application:
name: cloud-gateway #微服务名称,建议有
cloud:
gateway:
routes: # 路由可以有多个
- id: payment-router1 # 我们⾃定义的路由 ID,保持唯⼀
uri: http://localhost:8001 # ⽬标服务地址 部署多实例) 动态路由:uri配置的应该是⼀个服务名称,⽽不应该是⼀个具体的服务实例的地址
#uri: lb://cloud-payment-service # ⽬标服务地址 部署多实例) 动态路由:uri配置的应该是⼀个服务名称,⽽不应该是⼀个具体的服务实例的地址
# gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
predicates: #断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默 认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
- Path=/payment/get/**
- id: payment-router2 # 我们⾃定义的路由 ID,保持唯⼀
uri: http://127.0.0.1:8001 #服务名不要用下划线
#uri: lb://cloud-payment-service #匹配后提供的路由地址
predicates: #断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默 认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
- Path=/payment/lb/**
#filters:
#- StripPrefix=1 #可以去掉url中的占位后转发路由 可以去掉api后转发
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #是否抓取注册信息。
service-url:
defaultZone: http://eureka7003.com:7003/eureka #单机版只写自己本身
# 集群版 写注册中心所有的 机器地址 (有2个注册中心集群 就写两个地址)
#defaultZone: http://eureka7003.com:7003/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
测试 :
添加网关前测试:localhost:8001/payment/get/1
添加网关后测试:localhost:9527/payment/get/1
测试负载均衡的方法:http://localhost:9527/payment/lb
getway网关漏由的两种配置:
(1)硬编码:不推荐
多个漏由配置:
(2)yml配置,推荐
五、通过微服务名来实现动态漏由:
修改pom文件:
server:
port: 9527 #服务端口号,建议一定有
spring:
application:
name: cloud-gateway #微服务名称,建议有
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建漏由的功能,利用微服务名进行漏由
routes: # 路由可以有多个
- id: payment-router1 # 我们⾃定义的路由 ID,保持唯⼀
#uri: http://localhost:8001 # ⽬标服务地址 部署多实例) 动态路由:uri配置的应该是⼀个服务名称,⽽不应该是⼀个具体的服务实例的地址
uri: lb://cloud-payment-service # ⽬标服务地址 部署多实例) 动态路由:uri配置的应该是⼀个服务名称,⽽不应该是⼀个具体的服务实例的地址
# gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
predicates: #断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默 认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
- Path=/payment/get/**
- id: payment-router2 # 我们⾃定义的路由 ID,保持唯⼀
#uri: http://127.0.0.1:8001 #服务名不要用下划线
uri: lb://cloud-payment-service #匹配后提供的路由地址
predicates: #断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。该接⼝包含多种默 认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
- Path=/payment/lb/**
#filters:
#- StripPrefix=1 #可以去掉url中的占位后转发路由 可以去掉api后转发
eureka:
client:
register-with-eureka: true #false表示不向注册中心注册自己。
fetch-registry: true #是否抓取注册信息。
service-url:
defaultZone: http://eureka7003.com:7003/eureka #单机版只写自己本身
# 集群版 写注册中心所有的 机器地址 (有2个注册中心集群 就写两个地址)
#defaultZone: http://eureka7003.com:7003/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
注意,启动两个提供者的的微服务的。和一个注册中心,一个网关微服务的。
然后修改pom文件。
测试:http://localhost:9527/payment/lb
发现可以实现负载均衡,如下:
六、predicate的使用:
after时间:在测试包下我们生成一个时间的工具类:用于打印时间格式
可以发送请求的工具:
输入cmd:
七、Filter的使用:
参考:https://juejin.cn/post/6844903795973947400
过滤器是什么:
(1)自定义全局过滤器:
实现两个接口: GatewayFilter,Ordered
1.在网关的微服务中编写Filter:filter.MyGateWayFilter
//过滤器1代码:
@Component
public class MyGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("1111111111111111");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
//过滤器2代码:
测试过滤器1:
看控制台打印。
测试过滤器2: