SpringCloud&alibaba学习笔记系列
该笔记仅作为个人学习使用。
第八章Gateway新一代服务网关
8.1概述简介
官网:
上一代网关zuul:https://github.com/Netflix/zuul/wiki
当前Gateway网关:https://spring.io/projects/spring-cloud-gateway
8.1.1是什么
Cloud全家桶中有一个很重要的组件就是网关,在1.X版本中都是采用的Zuul网关;但在2.x版本中,zuul的升级一直划水,SpringCloud最后自己养了一个网关代替Zuul,那就是SpringCloud Gateway一句话:gateway是元zuul1.x版本的替代。
Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是代替Zuul,在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,任然还是使用Zuul1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
SpringCloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控、指标和限流。
8.1.2能干什么
1、反向代理
2、鉴权
3、流量控制
4、熔断
5、日志监控
。。。
8.1.3微服务架构中网关的位置
8.2三大核心概念
8.2.1Route路由
路由是构建网关的基础模块,它由ID,目标URI,一系列断言和过滤器组成,如果断言为true则匹配该路由。
8.2.2Predicate断言
输入类型是一个ServerWebExchange。我们可以使用它来匹配HTTP请求的任何内容,例如headers或者参数。
8.2.23Filter过滤器
Gateway的Filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。使用过滤器Filter可以对请求被路由之前或者之后队请求进行修改。
总体:
web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;而filter,就可以理解一个无所不能的拦截器。有了这两个元素,再加上目标URI,就可以实现一个具体的路由了。
8.3Gateway工作流程
客户端向SpringCloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求向匹配的路由,将其发送到Gateway Web Handler。
Handler在通过指定的过滤器链在来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)之星业务逻辑。
Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
8.4Gateway入门配置
8.4.1新建cloud-gateway-gateway9527
8.4.2POM文件
<?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>springcloud2020</artifactId>
<groupId>com.jzt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--需要删除web和actuator-->
<!--<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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>
<version>1.18.14</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
8.4.3YML配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
client:
register-with-eureka: true #表示将自己注册到eurekaserver上,默认为true
fetch-registry: true #是否从Eurekaserver抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true,才能配合ribbon使用负载均衡
service-url:
defaultZone: http://localhost:7001/eureka #单机版
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
8.4.4主启动类
package com.jzt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 功能描述:
*
* @Author: sj
* @Date: 2020/12/7 22:43
*/
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class, args);
}
}
8.4.5YML新增网关配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes: #路由配置
- id: payment_routh #路由的ID,没有固定的规则,但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates: #谓词匹配
- Path=/payment/get/** #匹配路径
- id: payment_routh2
uri: http://localhost:8001 #目标路径
predicates: #谓词匹配
- Path=/payment/lb/** #匹配路径
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true #表示将自己注册到eurekaserver上,默认为true
fetch-registry: true #是否从Eurekaserver抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true,才能配合ribbon使用负载均衡
service-url:
defaultZone: http://localhost:7001/eureka #单机版
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
8.4.6测试
首先我们不通过网关直接访问8001微服务:http://localhost:8001/payment/get/1
然后我们通过网关访问:http://localhost:9527/payment/get/1
显然,我们成功的通过网关访问到了8001微服务。
8.4.7YML配置说明
当我们访问http://localhost:9527/payment/get/1时,首先会对9527中的路由规则进行匹配,根据/payment/get/1断言规则匹配到路由id为payment_routh;
然后根据uri为http://localhost:8001确定访问的微服务的路由地址。最终确定转发的路由为http://localhost:8001/payment/get/1。
8.4.8Gateway路由配置的两种方式
Gateway路由配置有两种方式:一种是通过yml配置文件,还有一种是通过硬编码的方式;
1、yml配置文件
见前文。
2、硬编码方式
需要自己在代码中注入RouteLocator的Bean。
下面我们自己手动实现一个,通过9527网关访问http://news.baidu.com/guonei。
配置路由:
package com.jzt.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 功能描述:
*
* @Author: sj
* @Date: 2020/12/9 7:36
*/
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator1(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route1", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
//可添加不同的规则
//...
}
然后访问:http://localhost:9527/guonei
可见通过这种方式也能实现路由配置功能。
8.5Gateway配置动态路由
通过上面小节的入门配置,我们已经可以通过网关访问8001微服务了。但是有一个问题,我们在配置文件中将要访问的路由写死了。例如uri: http://localhost:8001 就是我们要访问的路由地址,但是在微服务的架构下,一个服务可能有多个节点实例的情况,也就是支付服务可以有多实例,如8001,8002等。那这个时候,就需要我们通过配置动态路由的方式来进行配置了。具体就是通过注册中心查询到服务实例名称,然后根据名称来获取路由地址。如下图:
下面我们进行具体操作:
1)在pom文件中添加注册中心
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
这个我们之前的意见添加过了。
2)修改yml配置文件
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes: #路由配置
- id: payment_routh #路由的ID,没有固定的规则,但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates: #谓词匹配
- Path=/payment/get/** #匹配路径
- id: payment_routh2
#uri: http://localhost:8001 #目标路径
uri: lb://cloud-payment-service
predicates: #谓词匹配
- Path=/payment/lb/** #匹配路径
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true #表示将自己注册到eurekaserver上,默认为true
fetch-registry: true #是否从Eurekaserver抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true,才能配合ribbon使用负载均衡
service-url:
defaultZone: http://localhost:7001/eureka #单机版
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
3)测试
启动7001,8001,8002和网关9527;
多次访问:http://localhost:9527/payment/lb
发现通过网关访问到了cloud-payment-service微服务,同时实现了负载均衡。
8.6Predicate的使用
Predicate(断言) 来源于 Java 8,是Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
SpringCloud Gateway将路由作匹配作为Spring WebFlux HanderMapping基础架构的一部分。其中包括许多内置的Route Predicate工程。所有这些Predicate都与Http请求的不同属性匹配。多个Route Predicate工程进行组合。
当我们启动9527网关服务是,能够看到以下日志:
以下是常用的断言Route Predicate:
说白了 Predicate 就是为了实现一组匹配规则,方便让请求过来找到对应的 Route 进行处理,接下来我们简单挑选 Spring Cloud GateWay 内置两种 Predicate 介绍其使用。
8.6.1After Route Predicate
After Route Predicate使用的是时间作为匹配规则,只要当前时间大于设定时间,路由才会匹配请求。
yml文件配置:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes: #路由配置
- id: payment_routh #路由的ID,没有固定的规则,但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates: #谓词匹配
- Path=/payment/get/** #匹配路径
- id: payment_routh2
#uri: http://localhost:8001 #目标路径
uri: lb://cloud-payment-service
predicates: #谓词匹配
- Path=/payment/lb/** #匹配路径
- After=2020-12-13T10:33:33.789+08:00 #这个路由规则会在东8区的2020-12-13 10:33:33后,将请求都转跳到匹配到的路径中。
#这个路由规则会在东8区的2020-12-13 10:33:33后,将请求都转跳到匹配到的路径中。
8.6.2Cookie Route Predicate
Cookie Route Predicate 可以接收两个参数,一个是 Cookie name ,一个是正则表达式,路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes: #路由配置
- id: payment_routh #路由的ID,没有固定的规则,但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates: #谓词匹配
- Path=/payment/get/** #匹配路径
- id: payment_routh2
#uri: http://localhost:8001 #目标路径
uri: lb://cloud-payment-service
predicates: #谓词匹配
- Path=/payment/lb/** #匹配路径
# - After=2020-12-13T10:33:33.789+08:00 #这个路由规则会在东8区的2020-12-13 10:33:33后,将请求都转跳到匹配到的路径中。
- Cookie=username,sj
使用 curl 测试,命令行输入:
curl [http://localhost:9527 --cookie “username=sj”
则会返回页面代码,如果去掉–cookie username=sj",后台汇报 404 错误。
8.7Filter的使用
8.7.1是什么
路由过滤器可以用于修改进入的Http请求和返回的Http响应,路由过滤器只能指定路由进行使用。
SpringCloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工程类来产生。
Spring Cloud Gateway 内置的过滤器工厂一览表如下:
GatewayFilter工厂同上一篇介绍的Predicate工厂类似,都是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。
例如我们进行如下配置:
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes: #路由配置
- id: payment_routh #路由的ID,没有固定的规则,但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates: #谓词匹配
- Path=/payment/get/** #匹配路径
- id: payment_routh2
#uri: http://localhost:8001 #目标路径
uri: lb://cloud-payment-service
filters:
- AddRequestHeader=X-Request-Foo, Bar
predicates: #谓词匹配
- Path=/payment/lb/** #匹配路径
# - After=2020-12-13T10:33:33.789+08:00 #这个路由规则会在东8区的2020-12-13 10:33:33后,将请求都转跳到匹配到的路径中。
- Cookie=username,sj
上面的配置中,我们添加了一个过滤器AddRequestHeaderGatewayFilterFactory(AddRequestHeader),作用是AddRequestHeader过滤器工厂会在请求头加上一对请求头,名称为X-Request-Foo,值为Bar。
8.7.2自定义过滤器
在springCloud Gateway中,自定义过滤器需要实现GatewayFilter和Ordered2个接口。写一个MyLogGatewayFilter,代码如下:
package com.jzt.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 功能描述:
*
* @Author: sj
* @Date: 2020/12/13 11:30
*/
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
/**
* 该过滤方法作用是校验是否有合法的username
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("****{MyLogGatewayFilter} start.");
String username =exchange.getRequest().getQueryParams().getFirst("username");
if(username == null){
log.info("**** 非法用户!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
测试:
访问http://localhost:9527/payment/lb?username=zhangsan
由于我们带上了username=zhangsan,满足过滤规则,所以放行,得到正确放回;
访问:http://localhost:9527/payment/lb
当我们没有带上username=zhangsan,就会被过滤器拦截,直接返回错误响应。