GateWay简介
Spring Cloud GateWay是Spring Cloud一个全新项目,目的是取代Netflix Zuul,基于 Spring5.0 + SpringBoot2.0 +WebFlux
(基于高性能的Reactor模式响应式通信框架Netty,异步非阻塞模型)等技术开发,性能高于Zuul,官方测试GateWay是Zuul的1.6倍,旨在为微服务架构提供一种简单有效的统一的API路由管理方式。
Spring Cloud GateWay提供了统一分路由方式(反向代理)。
同时,基于Filter(定义过滤器对请求过滤)链的方式提供了网关基本功能,例如:鉴权、流量控制、熔断、路径重写、日志监控等。
网关在架构中的位置
图例说明:
(1)Nginx负载均衡器:主要是完成对下游网关组件的负载,网关建立集群主要是为了实现高可用性。
(2)API路由:网关会转发请求到下游的微服务。
GateWay核心概念
Zuul 1.x - 阻塞式IO Zuul 2.x - 基于Netty 非阻塞IO
Spring Cloud GateWay天生是异步非阻塞的,基于Reactor模型。
一个请求,网关会根据一定的条件(断言)进行路由匹配;
匹配成功后就可以将请求转发到指定的服务;
在这个请求过程中,可以进行一些控制(限流、日志、黑白名单等);
- 路由:网关的基础部分,网关的基础工作单元。路由由一个ID、一个目标URL(最终路由到的地址)、一系列的断言(匹配条件判断)和Filter过滤器(精细化控制组成)。如果断言为true,则匹配该路由。
- 断言:参考java8中的断言
java.util.function.Predicate
,可以通过匹配http请求中所有内容(请求头、请求参数等), 来确定请求的最终路由。 — 匹配条件 - 过滤器:标准的Spring webFilter, 使用过滤器,可以在请求之前或者之后执行代码逻辑。— 拦截器
GateWay工作过程
(1)客户端向Spring Cloud GateWay发起请求;
(2) 请求会在 GateWay Handler Mapping中找到与请求相匹配的路由,并将其发送给GateWay Web Handler;
(3)Handler 通过指定的过滤器链(pre)来将请求发送到我们实际服务执行业务逻辑;
(4)业务处理完毕返回,会通过请求后的过滤器链(post)将响应内容进行处理;
(5) 返回最终的响应结果给客户端。
pre类型的Filter: 可以做参数校验,权限校验,流量监控,日志输出,协议转换等。 post类型的Filter:可以做响应内容修改,响应头修改,日志输出 ,流量监控等。
GateWay应用
使用网关对下游的微服务进行代理(相当于隐藏下游微服务,对外暴露网关)。
创建网关工程,导入依赖
GateWay不需要使用web模块,引入的是 WebFlux
<?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>lagou-cloud-gateway-9002</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-eurekaclient</artifactId>
</dependency>
<!--GateWay ⽹关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-startergateway</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>
<!-- Actuator可以帮助你监控和管理Spring Boot应⽤-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starteractuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<!--spring cloud依赖版本管理-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-clouddependencies</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-mavenplugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml配置
server:
port: 9002
eureka:
client:
serviceUrl: # eureka server的路径
#把 eureka 集群中的所有url都填写了进来,也可以只写⼀台,因为各个eureka server可以同步注册表
defaultZone: http://lagoucloudeurekaservera:8761/eureka/,http://lagoucloudeurekaserverb:8762/eureka/
instance:
#使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
spring:
application:
name: lagou-cloud-gateway
cloud:
gateway:
routes: # 路由可以有多个
# 我们⾃定义的路由 ID,保持唯⼀
- id: service-autodeliver-router
# ⽬标服务地址(部署多实例)动态路由:uri配置的应该是⼀个服务名称,⽽不应该是⼀个具体的服务实例的地址
# gateway⽹关从服务注册中⼼获取实例信息然后负载后路由
uri: http://127.0.0.1:8096
# 断⾔:路由条件,Predicate 接受⼀个输⼊参数,返回⼀个布尔值结果。
# 该接⼝包含多种默认⽅法来将 Predicate 组合成其他复杂的逻辑(⽐如:与,或,⾮)。
predicates:
- Path=/autodeliver/**
- id: service-resume-router
uri: http://127.0.0.1:8081 # ⽬标服务地址
predicates:
- Path=/resume/**
filters:
# 跳过uri的第一个路径
# http://localhost:9002/resume/openstate/1545132 -> http://127.0.0.1:8081/openstate/1545132
- StripPrefix=1
GateWay 路由规则
时间点后匹配
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: after_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
时间区间匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver]
指定Cookie正则匹配指定值
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
指定Header正则匹配指定值
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
请求Host匹配指定值
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
请求Method匹配指定请求方式
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
GateWay动态路由详解
GateWay支持自动从注册中心中获取服务列表并访问,即所谓的动态路由
实现步骤
(1)pom.xml中添加注册中心客户端依赖(eureka-client: 获取注册中心服务列表)
(2) 动态路由配置application.yml
spring:
cloud:
gateway:
routes:
- id: service-autodeliver-router
uri: lb://service-autodeliver # 以lb://开头,代表从注册中心获取服务
GateWay过滤器
过滤器简介
按照过滤器生命周期(影响时机点)角度来说,分为两类过滤器 | 生命周期时机点 | 作用 | | —- | —- | | pre | 这种过滤器在请求被路由之前调用。
可利用这种过滤器实现身份验证、在集群中选择 请求的微服务,记录调试信息等。 | | post | 这种过滤器在路由到微服务以后执行。
可用来为相应添加标准的HTTP Header,收集统计信息和指标,将响应从微服务发送给客户端等。 |从过滤器类型角度,分为两类过滤器 | 过滤器类型 | 影响范围 | | —- | —- | | GateWayFilter | 应用到单个路由上 | | GlobalFilter | 应用到所有路由上 |
predicates:
- Path=/resume/**
filters:
# 跳过uri的第一个路径
# http://localhost:9002/resume/openstate -> http://127.0.0.1:8081/openstate
- StripPrefix=1 # 可以去掉resume之后转发
自定义全局过滤器实现IP访问限制
当请求过来时,判断发送请求的客户端IP,如果在黑名单中,拒绝访问。
自定义GateWay全局过滤器时,实现Global Filter接口,通过全局过滤器可以实现黑白名单、限流等功能。
/**
* 定义全局过滤器,会对所有路由⽣效
*/
@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实例来实现高可用,在GateWay的上游使用Nginx等负载均衡设备进行负载转发以达到高可用的目的。
#配置多个GateWay实例
upstream gateway {
server 127.0.0.1:9002;
server 127.0.0.1:9003;
}
location / {
proxy_pass http://gateway;
}