笔记整理自【尚硅谷】周阳SpringCloud框架开发教程
服务网关还可以用Zuul网关,但是Zuul网关由于一些维护问题,所以这里我们学习Gateway网关。
SpringCloud全家桶里有个很重要的组件就是网关, 在1.x的版本中都是采用Zuul网关;但在2.x版本中,Zuul的升级一直跳票,SpringCloud最后自己研发了一个网关代替Zuul,那就是SpringCloud Gateway,一句话:Gateway是原zuul1.x版的替代。
SpringCloud Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。Gateway旨在提供一种简单而有效的方式来对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链的方式提供了网关基本的功能,例如:安全、监控/指标、限流等。
一句话:SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
源码架构
反向代理、鉴权、流量控制、熔断、日志监控等等。
一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能Zuul都没有用起来也非常的简单便捷。
Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x,但 Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
多方面综合考虑Gateway是很理想的网关选择。
在SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul:
SpringCloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
学过尚硅谷web中期课程都知道一个题目,Servlet的生命周期?Servlet由Servlet Container
进行生命周期管理。Container启动时构造servlet对象并调用servlet init()
进行初始化;Container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()
。Container关闭时调用servlet destory()
销毁servlet;
上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入Servlet Container时,Servlet Container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压测),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势
所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端
WebFlux是什么
传统的Web框架,比如说:Struts2,SpringMVC等都是基于Servlet API与Servlet容器基础之上运行的。
但是,在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)
Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
路由是构建网关的基本模块,它由ID、目标URI、一系列的断言和过滤器组成,如果断言为true则匹配路由。
参考的是Java8的java.util.function.Predicate
,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言匹配则进行路由。
Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由之前或者之后对请求进行修改。
总体
客户当发送Web请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化的控制,而断言Predicate就是这些匹配条件;
而过滤器Filter,就可以理解为一个无所不能的拦截器。有了断言和过滤器,再加上目标的uri,就可以实现一个具体的路由了。
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")
或 之后("post")
执行业务逻辑。
Filter在("pre")
类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;在("post")
类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
核心逻辑:路由转发 + 执行过滤器链
建Module
cloud-gateway-gateway9527
改POM
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
后面可能会遇到一个小坑,就是Gateway不要引入spring-boot-starter-web
,需要之前经常引入的这两个依赖删除:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
写YML
主启动
9527网关如何做路由映射?
➢ cloud-provider-payment8001看看controller的访问地址(get、lb)
➢ 我们目前不想暴露8001端口,希望在8001外面套一层9527
YML新增网关配置
测试
➢ 添加网关前:http://localhost:8001/payment/get/31
➢ 添加网关后:http://localhost:9527/payment/get/31
访问说明
这样的话用网关对微服务进行路由访问,就可以不再对外暴露微服务的真实地址,而是统一暴露网关的地址。
YML配置说明
Gateway网关路由有两种配置方式:
在配置文件yml中配置(见前面的步骤)
代码中注入 RouteLocator
的Bean
用编码的方式,实现通过9527网关对 百度新闻http://news.baidu.com/guonei 的访问
➢ Config
上面的配置类注入RouteLocator
的Bean,其配置的一个id为"path_route"的路由,当访问地址 http://localhost:9527/guonei 时,该路由会将访问自动转发到 http://news.baidu.com/guonei:
在上面的yml配置文件中,我们把路由地址写死了,这明显应该是程序中避免的。所以更好的方式是通过微服务名来实现动态路由。
以前使用Ribbon实现负载均衡,现在使用网关实现负载均衡。
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
启动
一个eureka7001 + 两个服务提供者8001/8002
YML
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri。
测试
http://localhost:9527/payment/lb
8001/8002两个端口切换
我们注意:Predicates
这是一个复数,其实有多种Predicate。 我们这里用的Predicate的是[Path],它是路由规则的其中一个。作用是,如果cloud-payment-service 的微服务实例中有/payment/get/**的接口,就会返回true,路由规则生效。
启动我们的gateway9527并观察:
Route Predicate Factories是什么?
每一个断言Predicate都有它独特的规则,多个Predicate断言是一个与&组合
官网对Gateway的断言每个都写了例子。
常用的Route Predicate
常用的Predicate断言使用 参考
小总结
说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
生命周期
➢ pre
➢ post
种类
➢ GatewayFilter
单一的,31种
➢ GlobalFilter
全局的
常用的GatewayFilter
➢ AddRequestParameter
➢ …
自定义全局GlobalFilter
作用:全局日志记录,统一网关鉴权,…
创建MyLogGateWayFilter
实现两个接口 implements GlobalFilter
, Ordered
前者实现了全局过滤器,而后者规定了过滤器的执行顺序,该顺序数字越小,过滤器越先被执行。
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("************come in MyLogGateWayFilter: " + new Date());
// 取出请求参数的uname对应的值
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
// 如果uanme为空,就直接过滤掉,不走路由
if (uname == null) {
log.info("************* 用户名为Null 非法用户 o(╥﹏╥)o");
// 判断该请求不通过时:给一个回应,返回
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
// 反之,调用下一个过滤器,也就是放行:在该环节判断通过的exchange放行,交给下一个filter判断
return chain.filter(exchange);
}
/**
* 这个过滤器的加载顺序,数字越小,优先级越高
* 设置这个过滤器在Filter链中的加载顺序。
*/
@Override
public int getOrder() {
return 0;
}
}
测试
➢ 正确
http://localhost:9527/payment/lb?uname=z3
➢ 错误
http://localhost:9527/payment/lb
没有参数uname,无法正常使用转发