服务网关
主要存在两个技术
Netflix开发的zuul 和 SpringCloud的gateway这里主要介绍Gateway
为什么用GateWay
但是zuul在使用中出现问题。领军人物跳槽了,zuul2开发周期太慢。在这个期间Spring开发出了Gateway,服务网关组件
GateWay,基于SpringBoot2,WebFlux基础上开发的。
webflux是SpringMVC的一段升级
Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
Spring Cloud Gateway 与 Zuul的区别
在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:
1、Zuul 1.x,是一个基于阻塞 I/ O 的 API Gateway
2、Zuul 1.x 基于Servlet 2. 5使用阻塞架构它不支持任何长连接(如 WebSocket) Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx 用C++ 实现,Zuul 用 Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差。
3、Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway 的 RPS(每秒请求数)是Zuul 的 1. 6 倍。
4、Spring Cloud Gateway 建立 在 Spring Framework 5、 Project Reactor 和 Spring Boot 2 之上, 使用非阻塞 API。
5、Spring Cloud Gateway 还 支持 WebSocket, 并且与Spring紧密集成拥有更好的开发体验
GateWay能做什么
所有微服务的入口。
反向代理,鉴权,流量控制,熔断,日志监控
三大核心
路由(Route)
路由是构建网关的基本模块,它由ID,目标URL,一系列的断言和过滤器组成,如果断言为true则匹配该路由
断言(Predicate)
参考JDK8的Predicate类
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数)
如果请求与断言相匹配则进行路由
过滤器(filter)
值的是Spring框架中GateWayFilter的实例,使用过滤器,可以在路由前者或后者对请求进行修改
总体
- web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
- predicate就是我们的匹配条件;
- 而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了
执行流程
- 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
- Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。 - Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
GateWay搭建
首先搭建一个GateWay服务网关
引入依赖
<!--gateway-->
<!--gateway的主要依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--同时也是需要注册中心的-->
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
正常的将GateWay注册到注册中心中
yaml
server:
port: 7005
# 这里的注册中心可以换成自己喜欢的
#将GateWay放入注册中心中
spring:
application:
name: gateWay
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
主启动
@SpringBootApplication
//将Gateway放入注册中心
@EnableDiscoveryClient
public class GateWayMain {
public static void main(String[] args) {
SpringApplication.run(GateWayMain.class,args);
}
}
我们可以通过网关,在其他服务的请求,进行一个包装。不需要再指定明确的ip地址和端口。通过指定网关的ip端口就可以访问
yaml修改
再网关中进行指定可以访问的路由。
我们就可以通过路由进行访问指定的请求。
从而达到所有的请求都可以走GateWay的服务网关
server:
port: 7005
#将GateWay放入注册中心中
spring:
application:
name: gateWay
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
gateway:
routes:
- id: mouduleOneM1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8881 #匹配后提供服务的路由地址
predicates:
- Path=/one/** # 断言,路径相匹配的进行路由
- id: mouduleTwoM1 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8882 #匹配后提供服务的路由地址
predicates:
- Path=/twoshow/** # 断言,路径相匹配的进行路由
通过上面我们可以看到,如果我们一个一个请求对应路由还是比较麻烦的。
我们可以通过控制器中设置一个总的请求。放入路由中。从而进行匹配
再控制器类名上使用@RequestMapper(“”)即可
我们出来使用yaml进行配置路由,还可以使用编码的方式。
不过编码的配置类方式,还是比较复杂,可以自行了解
动态路由
上面可以看到我们再路由路径是写死的。如果我们此时有一个服务集群我们怎么进行路由呢
可以再服务网关中开启动态路由。URL指定服务名称。指定LB负载均衡
将会再该服务中自动的去查找匹配的真正IP和端口
通过consul可以看到此时moduleOne服务存在两个实例。进行测试动态路由
yaml修改即可
spring:
application:
name: gateWay
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: mouduleOneM1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8881 #匹配后提供服务的路由地址
uri: lb://moduleOneprovider #匹配后提供服务的路由地址
predicates:
- Path=/one/** # 断言,路径相匹配的进行路由
断言Predicate
所谓的断言就是进行判断,请求是否否和条件,从而来判断是真是假。是否可以进行跳转。
routes:
- id: mouduleOneM1 #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8881 #匹配后提供服务的路由地址
uri: lb://moduleOneprovider #匹配后提供服务的路由地址
predicates:
- Path=/one/** # 断言,路径相匹配的进行路由
这里通过断言可以看出来断言是一个数组或集合,可以存放多个条件。
path表示路径。只有设定的多个条件都满足才可以进行网关的跳转。
断言的各种条件
After 路由断言 Factory
指定日期时间之后
- After Route Predicate Factory采用一个参数——日期时间。在该日期时间之后发生的请求都将被匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
Method 路由断言 Factory
指定请求方式(GET/POST)
- Method 路由断言 Factory只包含一个参数: 需要匹配的HTTP请求方式
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://example.org
predicates:
# 所有GET请求都将被路由
- Method=GET
Path 路由断言 Factory
指定路径
- Path 路由断言 Factory 有2个参数: 一个Spring
PathMatcher
表达式列表和可选matchOptionalTrailingSeparator
标识 .
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://example.org
predicates:
# segment为Map类型变量
- Path=/foo/{segment},/bar/{segment}
# 也可以直接写出 **代表所有请求都通过
# - Path=/consumer/**
Query 路由断言 Factory
指定参数名
- Query 路由断言 Factory 有2个参数: 必选项
param
和可选项regexp
.
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://example.org
predicates:
- Query=baz
还存在很多
过滤器Filter
可以在请求的前后,对请求进行处理。从而达到路由,断言的操作
允许进行请求进行处理请求
Filter生命周期
pre:表示在路由前 请求内容
post:在路由后,响应内容
Filter种类
GateWayFilter:存在31种,针对选中的路由
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
GlobalFilter:存在11种,针对全局的路由
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters: # 默认过滤器,会对所有的路由请求都生效
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
自定义全局过滤器
GlobalFilter:可以实现具体功能,(获取响应体,请求体)(判断)(放行等)
Ordered:指定级别
硅谷代码展示
@Component //必须加,必须加,必须加
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
System.out.println("time:"+new Date()+"\t 执行了自定义的全局过滤器: "+"MyLogGateWayFilter"+"hello");
//获取请求体中存在的数据
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
// 如果不存在给出警告
if (uname == null) {
System.out.println("****用户名为null,无法登录");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
//存在给与放行
return chain.filter(exchange);
}
//设置过滤器级别
@Override
public int getOrder()
{
return 0;
}
}
可以通过代码测试