Spring Cloud GateWay是Spring Cloud的一个全新项目,目标是取代Netflix Zuul,它基于 Spring5.0+SpringBoot2.0+WebFlux(基于高性能的Reactor模式响应式通信框架Netty,异步非阻塞模 型)等技术开发,性能高于Zuul,官方测试,GateWay是Zuul的1.6倍,旨在为微服务架构提供一种简 单有效的统一的API路由管理方式。
Spring Cloud GateWay不仅提供统一的路由方式(反向代理)并且基于 Filter(定义过滤器对请求过滤, 完成一些功能) 链的方式提供了网关基本的功能,例如:鉴权、流量控制、熔断、路径重写、日志监控 等。
网关在架构中的位置
Zuul1.x 阻塞式IO 2.x 基于Netty
Spring Cloud GateWay天生就是异步非阻塞的,基于Reactor模型
一个请求—>网关根据一定的条件匹配—匹配成功之后可以将请求转发到指定的服务地址;而在这个过 程中,我们可以进行一些比较具体的控制(限流、日志、黑白名单)
下面是来自官网的一张图,高度概述了Spring Cloud Gateway如何工作
客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配的 路由,将其发送到GateWay Web Handler;Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前 (pre)或者之后(post)执行业务逻辑。
Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类 型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。
注意:在没有端口的路由中定义的uri对于HTTP和HTTPS uri分别获得默认端口值80和443。
<?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">
<modelVersion>4.0.0</modelVersion>
<artifactId>test-cloud-gateway</artifactId>
<!--spring boot 父启动器依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--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>
<!--日志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!--引入Jaxb,开始-->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!--引入Jaxb,结束-->
<!-- Actuator可以帮助你监控和管理Spring Boot应用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!--链路追踪-->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>-->
</dependencies>
<dependencyManagement>
<!--spring cloud依赖版本管理-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!--编译插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
<!--打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
server:
port: 9002
eureka:
client:
serviceUrl:
# eureka server的路径
#把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server 可以同步注册表
defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
spring:
application:
name: lagou-cloud-gateway
cloud:
gateway:
routes: # 路由可以有多个
- id: service-autodeliver-router # 我们自定义的路由 ID,保持唯一
#uri: http://127.0.0.1:8096 # 目标服务地址 自动投递微服务(部署多实例) 动态路由:uri配置的应该是一个服务名称,而不应该是一个具体的服务实例的地址
uri: lb://lagou-service-autodeliver # gateway网关从服务注册中心获取实例信息然后负载后路由
predicates: # 断言:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默 认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
- Path=/autodeliver/**
- id: service-resume-router # 我们自定义的路由 ID,保持唯一
#uri: http://127.0.0.1:8081 # 目标服务地址
#http://localhost:9002/resume/openstate/1545132
uri: lb://lagou-service-resume
predicates:
- Path=/resume/**
filters:
- StripPrefix=1 # 可以去掉uri第一个位置(即resume)之后转发
Spring Cloud GateWay帮我们内置了很多Predicates功能,实现了各种路由匹配规则(通过 Header、请求参数等作为条件)匹配到对应的路由。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
这条路由规则将把80%的流量转发到weighthigh.org,并将20%的流量转发到weighlow.org
GateWay支持自动从注册中心中获取服务列表并访问,即所谓的动态路由,实现步骤如下
spring:
application:
name: lagou-cloud-gateway
cloud:
gateway:
routes: # 路由可以有多个
- id: service-autodeliver-router # 我们自定义的路由 ID,保持唯一
#uri: http://127.0.0.1:8096 # 目标服务地址 自动投递微服务(部署多实例) 动态路由:uri配置的应该是一个服务名称,而不应该是一个具体的服务实例的地址
uri: lb://lagou-service-autodeliver # gateway网关从服务注册中心获取实例信息然后负载后路由
predicates: # 断言:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默 认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
- Path=/autodeliver/**
- id: service-resume-router # 我们自定义的路由 ID,保持唯一
#uri: http://127.0.0.1:8081 # 目标服务地址
#http://localhost:9002/resume/openstate/1545132
# 动态路由配置
uri: lb://lagou-service-resume
predicates:
- Path=/resume/**
filters:
- StripPrefix=1
注意:动态路由设置时,uri以 lb: //开头(lb代表从注册中心获取服务),后面是需要转发到的服务名称
从过滤器生命周期(影响时机点)的⻆度来说,主要有两个pre和post:
从过滤器类型的⻆度,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
过滤器方式比较多,详细可以参考官网:
https://docs.spring.io/spring-cloud-gateway/docs/3.0.0-SNAPSHOT/reference/html/#gatewayfilter-factories
GlobalFilter全局过滤器是使用比较多的过滤器,下面示范一个自定义全局过滤器
需求:自定义全局过滤器实现IP访问限制
/**
* 定义全局过滤器,会对所有路由生效
*/
@Slf4j
@Component // 让容器扫描到,等同于注册了
public class BlackListFilter implements GlobalFilter, Ordered {
// 模拟黑名单(实际可以去数据库或者redis中查询)
private static List<String> blackList = new ArrayList<>();
static {
blackList.add("0:0:0:0:0:0:0:1"); // 模拟本机地址
}
/**
* 过滤器核心方法
* @param exchange 封装了request和response对象的上下文
* @param chain 网关过滤器链(包含全局过滤器和单路由过滤器)
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 思路:获取客户端ip,判断是否在黑名单中,在的话就拒绝访问,不在的话就放行
// 从上下文中取出request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 从request对象中获取客户端ip
String clientIp = request.getRemoteAddress().getHostString();
// 拿着clientIp去黑名单中查询,存在的话就决绝访问
if(blackList.contains(clientIp)) {
// 决绝访问,返回
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
log.debug("=====>IP:" + clientIp + " 在黑名单中,将被拒绝访问!");
String data = "Request be denied!";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
return response.writeWith(Mono.just(wrap));
}
// 合法请求,放行,执行后续的过滤器
return chain.filter(exchange);
}
/**
* 返回值表示当前过滤器的顺序(优先级),数值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
网关作为非常核心的一个部件,如果挂掉,那么所有请求都可能无法路由处理,因此我们需要做GateWay的高可用。
GateWay的高可用很简单:可以启动多个GateWay实例来实现高可用,在GateWay的上游使用Nginx等负载均衡设备进行负载转发以达到高可用的目的。
启动多个GateWay实例(假如说两个,一个端口9002,一个端口9003),剩下的就是使用Nginx等完 成负载代理即可。示例如下:
#配置多个GateWay实例 upstream gateway {
server 127.0.0.1:9002;
server 127.0.0.1:9003; }
location / {
proxy_pass http://gateway;
}