Gateway入门实践
一、什么是GateWay
GateWay是Spring生态系统上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤功能,例如:限流、熔断、重试等。SpringCloud GateWay是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
二、GateWay的功能
反向代理、鉴权、流量控制、熔断、日志监控等
微服务架构中网关的位置:
三、为什么选择GateWay
1、netflix不太靠谱,zuul2.0一直跳票,迟迟不发布;
2、SpringCloud GateWay具有如下特性:
- 基于Spring Framework5,Project Reactor和SpringBoot2.0进行构建
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Hystrix的断路器功能
- 集成SpringCloud的服务发现功能
- 易于编写的Predicate(断言)和Filter(过滤器)
- 请求限流功能
- 支持路径重写
- 基于异步非阻塞模型上进行开发
3、SpringCloud GateWay与Zuul的区别 - Zuul 1.x是一个基于==阻塞 I/O ==的API Gateway
- Zuul 1.x基于servlet 2.5使用阻塞架构它不支持任何长连接(如websocket)Zuul的设计模式和Nginx较像,每次I/O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有一次加载较慢的情况,使得zuul的性能相对较差
- Zuul 2.x理念更先进,向基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul 2.x的性能较Zuul 1.x有较大提升。在性能方面,根据官方提供的基准测试,SpringCloud Gateway的RPS(每秒请求数)是Zuul的1.6倍
- SpringCloud Gateway建立在Spring Framework5、Project Reactor和Spring Boot 2之上,使用非阻塞API
- SpringCloud Gateway还支持WebSocket,并且与Spring紧密集成用于更好的开发体验
四、GateWay的核心概念
1、route(路由)
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
2、Predicate(断言)
参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头和请求参数),如果请求与断言相匹配则进行路由。
3、Filter(过滤器)
指的是Spring框架中,GateWayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是我们的匹配条件;而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上uri,就可以实现一个具体的路由。
五、GateWay的的工作流程
核心逻辑:路由转发+执行过滤器链
客户端向SpringCloud GateWay发出请求,然后GateWay Handler Mapping中找到请求相匹配的路由,将其发送到GateWay Web Handler。
Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
六、实战案例
1、新建module cloud-gateway-gateway9527
2、pom
注意:不要添加 web的依赖,与gateway里的web flux冲突
<?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>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入自定义的api通用包,可使用Payment支付Entity-->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
3、application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
4、启动类
package com.atguigu.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);
}
}
5、配置路由
cloud-provider-payment8001 controller的访问地址
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes: # 可以配置多个路由
- id: payment_routh # 路由id,没有固定规则但要求唯一
uri: http://localhost:8001 # 匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 路径相匹配的进行路由
- id: payment_routh2 # 路由id,没有
uri: http://localhost:8001 # 匹配后提供服务的路由地址
predicates:
- Path=/payment/lb # 路径相匹配的进行路由
6、测试
启动7001,8001,9527
添加网关前访问:
添加网关后:
可以发现,添加网关后,可以通过以下路径访问8001中的信息,http://localhost:9527/payment/get/11,不再暴露8001的端口。
7、路由的两种配置方式
- yml配置
spring:
cloud:
gateway:
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
- 注入RouteLocator的Bean
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
/*
* 代表访问http://localhost:9527/guonei
* 跳转到http://news.baidu.com/guonei
* */
routes.route("route1",
r->r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
- 测试结果
七、动态路由
在上述的案例中,我们将路由地址写死,在现实中,不可能只有一台微服务提供微服务,因此我们可以通过配置动态路由的方式实现。默认情况下,GateWay会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由功能。
在没有引入网关前,80通过ribbon访问8001、8002实现负载均衡。现将Ribbon替换为Gateway,客户端的请求统一发送到gateway,gateway将请求转发给8001、8002实现负载均衡。
1、9527注册进eureka
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、修改yml
注意:lb:uri协议,表示开启gateway的负载均衡功能
discovery:
locator:
enabled: true
开启动态路由功能
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-provider-service #匹配后提供服务的路由地址,lb后跟提供服务的微服务的名,不要写错
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-provider-service #匹配后提供服务的路由地址,lb后跟提供服务的微服务的名,不要写错
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
3、测试
8001、8002交替出现
八、Predicate
Predicate,是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
种类:
举例说明:
在yml中可以配置
After:
# 在该时间之后可以使用
- After=2020-05-26T17:07:03.043+08:00[Asia/Shanghai]
获取当前时区的时间
ZonedDateTime z = ZonedDateTime.now();// 默认时区
案例
Cookie:
# 查看有没有指定kv的cookie
- Cookie=username,wxh
案例:
使用curl命令测试,curl是postman 图形化界面的命令
九、Filter
Filter,过滤器,指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
1、生命周期
pre、post
2、种类
GatewayFilter、GlobalFilter
3、使用请参考官网API
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/#gatewayfilter-factories
4、自定义全局的GlobalFilter
两个主要的接口
implements GlobalFilter,Ordered
能干嘛
全局日志记录、统一网关鉴权
案例:
@Component
@Slf4j
public class MyLogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 判断有没有 uname 这个参数
log.info("自定义全局日志过滤器");
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
log.info("用户名非法");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/*
* int HIGHEST_PRECEDENCE = -2147483648;
int LOWEST_PRECEDENCE = 2147483647;
* 加载过滤器顺序
* 数字越小优先级越高
* */
@Override
public int getOrder() {
return 0;
}
}
测试结果: