Gateway网关
1. 概述简介
1.1 官网
1.2 是什么
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,就是gateway.
1.2.1 概述
- Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2 和 Project Reactor等技术.
- Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如: 熔断,限流,重试等.
- 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 1.x非Reactor模式的老版本.而为了提升网关的性能,SpringCloud Gateway 是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty.
- SpringCloud Gateway的目标是提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流.
1.2.2 总结
SpringCloud Gateway 使用的Webflux中的reactor-net统一响应式编程组件,底层使用了Net统一通讯框架.
1.3 能干嘛
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
1.4 微服务网关在哪里
1.5 有Zuul了怎么又出来了Gateway
1.5.1 为什么选择Gateway
-
Netflix不太靠谱,zuul2.0一直跳票,迟迟不发布
- 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖.而且很多功能Zuul都没有,用起来也非常的简单便捷.
- Gateway是基于异步非阻塞模型进行开发的,性能方面不需要担心.虽然Netflix早就发布了最新的Zuul 2.x,单SpringCloud貌似没有整合计划.而且Netflix相关组件都宣布进入维护期;不知前景如何.
- 多方面综合考虑Gateway是很理想的网关选择.
1.5.2 Gateway特性
- 基于Spring Framework 5,Project Reactor 和 Spring Boot 2.0进行构建;
- 动态路由:能够匹配任何请求属性;
- 可以对路由指定Predicate(断言)和Filter(过滤器);
- 集成Hystrix的断路器功能;
- 集成SpringCloud服务发现功能;
- 易于编写的Predicate(断言)和Filter(过滤器);
- 请求限流功能
- 支持路径重写
1.5.3 SpringCloud Gateway 与 Zuul 1.x的区别
在SpringCloud Finchley正式版之前,Spring Cloud推荐的网关是Netflix提供的Zuul;
- Zuul 1.x,是一个基于阻塞I/O的API Gateway
- Zuul 1.x基于Servlet2.5使用阻塞架构它不支持任何长连接(如 WebSocket) Zuul的设计模式和Nginx较像,每次I/O操作都是从工作线程选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有一次加载较慢的情况,使得Zuul的性能相对较差.
- Zuul 2.x理念更先进,相继与Netty非阻塞和支持长连接,但SpringCloud目前还没有整合.Zuul 2.x的性能较Zuul 1.x有较大提升.在性能方面,根据官方提供的基准测试, Spring Cloud Gateway的RPS(每秒请求数)是Zuul的1.6倍.
- SpringCloud Gateway建立在Spring Framework 5、Project Reactor 和 Spring Boot 2之上,使用非阻塞API.
- Spring Cloud Gateway还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验.
2. 三大核心概念
2.1 Route(路由)
- 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由.
2.2 Predicate(断言)
- 参考的是Java8的java.util.function.Predicate
- 开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
2.3 Filter(过滤)
- 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改.
2.4 总结
- web请求,通过一些匹配条件,定位到真正的服务节点.并在这个转发过程的前后,进行一些精细化控制.
- predicate就是我们的匹配条件;
- 而filter,就可以理解为一个无所不能的拦截器.有了这两个元素,再加上目标uri,就可以实现一个具体的路由了.
3. Gateway工作流程
3.1 官网总结
- 客户端向SpringCloud Gateway发出请求.然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler.
- Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回.
- 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑.
- Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等有着非常重要的作用。
3.2 核心逻辑
- 路由转发+执行过滤器链
4. 入门配置
5. 通过微服务名实现动态路由
6. Predicate的使用
6.1 是什么
- Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping 基础架构的一部分.
- Spring Cloud Gateway包括许多内置的Route Predicate工厂.所有这些Predicate都与HTTP请求的不同属性匹配.多个Route Predicate工厂可以进行组合
- Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route. Spring Cloud Gateway包含许多内置的Route Predicate Factories.
6.2 常用的Route Predicate
6.2.1 After Route Predicate
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
- After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
package com;
import java.time.ZonedDateTime;
public class T {
public static void main(String[] args) {
ZonedDateTime zdt = ZonedDateTime.now();//默认时区
System.out.println(zdt);
//2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
}
}
6.2.2 Before Route Predicate
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
- Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
6.2.3 Between Route Predicate
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
- Between=2020-12-16T16:02:16.747+08:00[Asia/Shanghai],2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
6.2.4 Cookie Route Predicate
curl http://localhost:9527/payment/lb #不带cookie访问
curl http://localhost:9527/payment/lb --cookie "username=zyc" #带cookie访问
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
- Between=2020-12-16T17:02:16.747+08:00[Asia/Shanghai],2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
- Cookie=username,zyc
6.2.5 Header Route Predicate
curl http://localhost:9527/payment/lb #404,不带请求头"X-Request-Id:1234"
curl http://localhost:9527/payment/lb -H "X-Request-Id:1234" #带"X-Request-Id:1234"
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
- Between=2020-12-16T17:02:16.747+08:00[Asia/Shanghai],2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Cookie=username,zyc
- Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
6.2.6 Host Route Predicate
curl http://localhost:9527/payment/lb -H "Host:www.atguigu.com"
curl http://localhost:9527/payment/lb -H "Host:news.atguigu.com"
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
- Between=2020-12-16T17:02:16.747+08:00[Asia/Shanghai],2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Cookie=username,zyc
# - Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.atguigu.com #host含有**.atguigu.com
6.2.7 Method Route Predicate
server:
port: 9527
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
- Between=2020-12-16T17:02:16.747+08:00[Asia/Shanghai],2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Cookie=username,zyc
# - Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.atguigu.com #host含有**.atguigu.com
- Method=Get # get请求才能通过
6.2.8 Path Route Predicate
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
6.2.9 Query Route Predicate
http://localhost:9527/payment/lb?username=1
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
# - After=2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Before=2020-12-16T17:02:16.747+08:00[Asia/Shanghai]
- Between=2020-12-16T17:02:16.747+08:00[Asia/Shanghai],2020-12-16T18:02:16.747+08:00[Asia/Shanghai]
# - Cookie=username,zyc
# - Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.atguigu.com #host含有**.atguigu.com
- Method=Get # get请求才能通过
- Query=username,\d+ #要有username参数,并且值为整数才能通过
7. Filter的使用
7.1 是什么
- 路由过滤器可用于修改进入的Http请求和返回的Http响应,路由过滤器只能指定路由进行使用.
- Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生.
7.2 Spring Cloud Gateway的Filter
7.2.1 生命周期, Only Two
- pre
- post
7.2.2 种类 Only Two
- GatewayFilter 单一的
- GlobalFilter 全局的
7.3 常用的GatewayFilter
spring:
application:
name: cloud-gateway-service
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用为服务名进行路由
routes:
- id: payment_route1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #lb负载均衡
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_route2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
filters:
- AddRequestParameter=X-Request-Id,1024 # 过滤器工厂会在匹配的请求头上加上一对请求头,名称为X-Request-Id值为1024
7.4 全局自定义过滤器
package com.atguigu.springcloud.config;
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;
import java.util.Date;
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********** come in MyLogGatewayFilter: " + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null){
log.info("****** 用户名为空,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
log.info("用户名为:" + uname);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; //数字越小优先级越高
}
}