微服务网关Zuul,听说SpringCloud不准备要我了,可是为什么面试还要天天问我?

1,550 阅读19分钟

原文首发地址:Zuul,听说SpringCloud不准备要我了,可是为什么面试还要天天问我?

Spring Cloud 微服务精彩系列

  1. 阿里面试官问我:到底知不知道什么是Eureka,这次,我没沉默
  2. 万字详解Ribbon架构,针对面试高频题多角度细说Ribbon
  3. 什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!
  4. 2万字好文全方位深入学习SpringCloud Fegin,面试不在彷徨
  5. Zuul,听说SpringCloud不准备要我了,可是为什么面试还要天天问我?

什么是Api网关?

我们可以将API网关看做是一个反向路由器,它让我们不需要关注内部的细节,API网关统一服务入口,可方便实现对平台众多服务接口进行管控。

Api网关所有的调用者的请求都接收过来,然后通过指定的路由机制转发到特定的服务实例上。

我们可以将网关看做成一组过滤器组成的集合,有一些与业务无关的公共逻辑可以抽象到网关中实现,如安全认证、限流熔断、日志监控,黑白名单,缓存,压力测试、灰度发布等。

为什么需要网关?

API网关给企业带来的好处

(1)网关层对外部和内部进行了隔离,保障了后台服务的安全性。

(2)对外访问控制由网络层面转换成了运维层面,减少变更的流程和错误成本。

(3)减少客户端与服务的耦合,服务可以独立运行,并通过网关层来做映射。

(4)通过网关层聚合,减少外部访问的频次,提升访问效率。

(5)节约后端服务开发成本,减少上线风险。

(6)为服务熔断,灰度发布,线上测试提供简单方案。

(7)便于进行应用层面的扩展。

上面展示的是API网关的核心架构图,在图中的核心功能模块中我们可以看到API网关内部帮我实现了这么的多功能模块,看到API网关帮我们实现了这么多的核心功能,我想大家应该都知道为什么需要使用网关了吧。

什么是Zuul?

Zuul作为微服务系统的网关组件,是从设备和网站到Netflix流应用程序后端的所有请求的前门。zuul作为整个应用的流量入口,接收所有的请求,如app、网页等,并且将不同的请求转发至不同的处理微服务模块。作为边缘服务应用程序,Zuul旨在实现动态路由,监控,弹性和安全性。

Zuul 主要应用场景

1.黑白名单:实现通过IP地址控制禁止访问网关功能,此功能是应用层面控制实现,再往前也可以通过网络传输方面进行控制访问。

2.日志:实现访问日志的记录,可用于分析访问、处理性能指标,同时将分析结果支持其他模块功能应用。

3.协议适配:实现通信协议校验、适配转换的功能。

4.身份认证:负责网关访问身份认证验证,此模块与“访问认证中心”通信,实际认证业务逻辑交移“访问认证中心”处理。

5.计流限流:实现微服务访问流量计算,基于流量计算分析进行限流,可以定义多种限流规则。

6.路由:路由是API网关很核心的模块功能,此模块实现根据请求,锁定目标微服务并将请求进行转发。此模块需要与“服务发布管理中心”通信。“服务发布管理中心”实现微服务发布注册管理功能,与其通信获得目标微服务信息。

Zuul核心架构剖析

Zuul网关架构图

  1. Zuul Servlet:zuul的servlet容器

  2. Zuul Filter Runner:zuul执行filter的处理器

  3. Pre routing Filter:zuul请求的前置过滤器

  4. Routing Filter:zuul请求的路由放行过滤器

  5. Post routing Filter:zuul请求的后置过滤器

  6. Request Context:zuul servlet的上下文

  7. Filter Loader:filter加载器

  8. Filter File Manager:filter内容管理器

  9. Filter Directory:filter过滤器存放路径

  10. Filter Publisher:发布filter的处理类

  11. Filter Persister:持久化filter的处理类

  12. Filter Poller:轮询Persister中的filter并将新filter推送至Filter Directory

Zuul请求处理生命周期

Zuul请求的生命周期如图所示,该图详细描述了各种类型的过滤器的执行顺序

  • http发送请求到zuul网关

  • zuul网关首先经过pre filter;

  • 验证通过后进入routing filter,接着将请求转发给远程服务,远程服务执行完返回结果,如果出错,则执行error filter;

  • 继续往下执行post filter;

  • 最后返回响应给http 客户端。

Zuul的实战演练

第一步,新建一个模块ZuulServer

引入zuul和eureka client的依赖

第二步,在Spring boot的主类上增加注解@EnableZuulProxy

第三步,添加application.yml配置

接下来启动,首先启动Eureka server,然后启动之前文章中创建的OrderService,最后启动刚刚创建的ZuulServer。

然后在浏览器请求http://localhost:8888/order-service/test 即可。

Zuul路由详解

Zuul的路由包含两种路由

1. 传统路由

所谓的传统路由配置方式就是在不依赖于服务发现机制的情况下,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现 API 网关对外不请求的路由。

2. 面向服务路由。****

传统路由的配置方式需要运维人员花费大量的时间来维护各个路由 path 与 url 的关系。为了解决这个问题,Spring Cloud Zuul 实现了与 Spring Cloud Eureka 的无缝整合,我们可以让路由的 path 不是映射具体的 url ,而是让它映射到某个具体的服务,而具体的 url 则交给 Eureka 的服务发现机制去自动维护。这类的路由便称为面向服务的路由。

传统路由配置

单实例配置

单实例的路由转发通过 zuul.routes..pathzuul.routes..url 参数对的方式进行配置。

比如下面配置实现了对符合 /zuul-service/** 规则的请求路径转发到 http://localhost:8888/ 地址的路由规则

多实例配置

多实例的路由转发通过 zuul.routes..pathzuul.routes..service-id 参数对的方式进行配置,其中 service-id 是由用户手工命名的服务名称,配合 ribbon.listOfServers 参数实现服务与实例的维护:

比如下面配置实现了对符合 /my-service/** 规则的请求路径转发到 http://localhost:8888/http://localhost:9999/ 两个实例地址的路由规则。

面向服务路由配置

默认规则

当我们为 Spring Cloud Zuul 构建的 API 网关服务引入 Spring Cloud Eureka 之后,它会为 Eureka 中的每个服务都自动创建一个默认路由规则:使用服务名作为 path 请求前缀。

比如 http://192.168.0.128:8888/order-service/order这个请求就会直接转发到 ORDER-SERVICE 服务实例上。

关闭默认规则

我们可以通过zuul.ignored-services=order-service 配置需要忽略的微服务(多个微服务通过逗号隔开),这样就不会自动对其创建路由规则。

我们也可以使用zuul.ignored-services=* 对所有的服务都不自动创建路由规则。

路径匹配

不论是使用传统路由的配置方式还是服务路由的配置方式,我们都需要为每个路由规则定义匹配表达式,也就是上面所说的path参数。在Zuul中,路由匹配的路径表达式采用了Ant风格定义。

Ant风格的路径表达式使用起来非常简单,它一共有下面这三种通配符:

通配符

说明

?

匹配任意的单个字符

*

匹配任意数量的字符

**

匹配任意数量的字符,支持多级目录

通配符实例演示:

/user-service/?

它可以匹配/user-service/之后拼接一个任务字符的路径,比如:/user-service/a、/user-service/b、/user-service/c

/user-service/*

它可以匹配/user-service/之后拼接任意字符的路径,比如:/user-service/a、/user-service/aaa、/user-service/bbb。但是它无法匹配/user-service/a/b

/user-service/**

它可以匹配/user-service/*包含的内容之外,还可以匹配形如/user-service/a/b的多级目录路径

忽略表达式

通过path参数定义的ant表达式已经能够完成api网关上的路由规则配置功能,但是为了更细粒度和更为灵活地配置理由规则,zuul还提供了一个忽略表达式参数zuul.ignored-patterns。

该参数可以用来设置不希望被api网关进行路由的url表达式。

注意:该参数在使用时还需要注意它的范围并不是针对某个路由,而是对所有路由。所以在设置的时候需要全面考虑url规则,防止忽略了不该被忽略的url路径。

比如我们启动order-service服务,访问:http://192.168.1.57:6069/order/index

可以使用api网关路由:http://192.168.1.57:6069/order-service/order/index

在zuul-service中配置

设置后如果访问 http://localhost:8888/order-service/index 将不会被正确路由,因为该路径符合zuul.ignored-patterns 参数定义的规则。而其他路径则不会有问题,比如http://localhost:8888/order-service/getOrderInfo。

路由前缀

Zuul通过****zuul.prefix参数来为路由规则增加前缀信息。

配置完前缀之后,之前访问路径都要增加 /javer123456前缀:

未加前缀时访问 user-service 服务:http://localhost:8888/user-service/hello
添加前缀后访问 hello-service 服务:http://localhost:8888/javaer123456/user-service/hello

Zuul通过strip-prefix代理前缀默认会从请求路径中移除,通过该设置关闭移除功能

stripPrefix=true 的时 (会移除)
http://127.0.0.1:8888/javaer123456/user/list**\->** http://192.168.1.100:8080/user/list)

stripPrefix=false的时(不会移除)
http://127.0.0.1:8888/javaer123456/user/list **->**http://192.168.1.100:8080/javaer123456/user/list

本地跳转

在 Zuul 实现的 API 网关路由功能中,还支持 forward 形式的服务端跳转配置。

比如我们在 API 网关项目中增加一个 /local/helloWorld 的接口

然后在 增加一个本地跳转的路由规则(forward-local):

当 API 网关接收到请求 /forward-local/helloWorld,它符合 forward-local 的路由规则,所以该请求会被 API 网关转发到网关的 /local/helloWorld请求上进行本地处理。

cookie与头信息

Spring Cloud Zuul 在请求路由时,通过zuul.sensitiveHeaders 参数定义,包括Cookie、Set-Cookie、Authorization 三个属性来过滤掉HTTP请求头信息中的一些敏感信息,防止它们被传递到下游的外部服务器。

但是如果我们将使用了Spring Security、Shiro 等安全框架构建的 Web 应用通过 Spring Cloud Zuul 构建的网关来进行路由时,Cookie 信息无法传递,会导致无法实现 登录和鉴权。

如何解决:

通过指定路由的参数来设置,仅对指定的web应用开启敏感信息传递

注意:指定路由的敏感头配置会覆盖掉全局设置 ‍‍‍‍‍‍

重定向问题

什么是Zuul的重定向问题?

我们在浏览器中通过 Zuul 网关发起了认证服务,认证通过后会进行重定向到某个主页或欢迎页面。此时,我们发现,在认证完成之后,但是发现重定向的这个欢迎页的 host 变成了这个认证服务的 host,而不是 Zuul 的 host,这是一个很严重的问题。

解决方法:

Zuul核心知识点---过滤器

过滤器可以说是zuul实现api网关功能最核心的部件,Zuul大部分功能都是通过过滤器来实现的。每一个进入zuul的http请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。

我们可以在ZuulFilter接口中看到定义的4个抽象方法

String filterType();
int filterOrder();
boolean shouldFilter();
Object run();

这是个抽象方法也就代表了过滤器的四个核心概念

(filterType)类型Type:定义在路由流程中,过滤器被应用的阶段

(filterOrder)执行顺序Execution Order:在同一个Type中,定义过滤器执行的顺序

(shouldFilter)条件Criteria:过滤器被执行必须满足的条件

(run)动作Action:过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。

类型Type

Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。

(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。

(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

核心过滤器详解

核心过滤器执行顺序

前置过滤器

1.ServletDetectionFilter

它的执行顺序为-3,是最先被执行的过滤器。

该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行的,还是通过ZuulServlet来处理运行的。

它的检测结果会以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法来判断请求处理的源头,以实现后续不同的处理机制。

一般情况下,发送到api网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/*路径访问的请求会绕过DispatcherServlet(比如之前我们说的大文件上传),被ZuulServlet处理,主要用来应对大文件上传的情况。

另外,对于ZuulServlet的访问路径/zuul/*,我们可以通过zuul.servletPath参数进行修改。

2.Servlet30WrapperFilter

它的执行顺序为-2,是第二个执行的过滤器,目前的实现会对所有请求生效,主要为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象。

3.FormBodyWrapperFilter

它的执行顺序为-1,是第三个执行的过滤器。该过滤器仅对两类请求生效,第一类是Context-Type为application/x-www-form-urlencoded的请求,第二类是Context-Type为multipart/form-data并且是由String的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果)。

而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。

4.DebugFilter

它的执行顺序为1,是第四个执行的过滤器,该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。

而它的具体操作内容是将当前请求上下文中的debugRoutingdebugRequest参数设置为true。

由于在同一个请求的不同生命周期都可以访问到这二个值,所以我们在后续的各个过滤器中可以利用这二个值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过参数的方式来激活这些debug信息以帮助分析问题,另外,对于请求参数中的debug参数,我们可以通过zuul.debug.parameter来进行自定义。

5.PreDecorationFilter

执行顺序是5,是pre阶段最后被执行的过滤器,该过滤器会判断当前请求上下文中是否存在forward.doserviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这二个信息就是根据当前请求的路由信息加载进来的)。

而当它的具体操作内容就是为当前请求做一些预处理,比如说,进行路由规则的匹配,在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些信息。

另外,我们还可以在该实现中找到对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如X-Forwarded-Host,X-Forwarded-Port

另外,对于这些头域是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值是true,所以zuul在请求跳转时默认会为请求增加X-Forwarded-*头域,包括X-Forwarded-Host,X-Forwarded-PortX-Forwarded-ForX-Forwarded-Prefix,X-Forwarded-Proto

也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。

路由过滤器

6.RibbonRoutingFilter

它的执行顺序为10,是route阶段的第一个执行的过滤器。

该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。

而该过滤器的执行逻辑就是面向服务路由的核心,它通过使用ribbon和hystrix来向服务实例发起请求,并将服务实例的请求结果返回。

7.SimpleHostRoutingFilter

它的执行顺序为100,是route阶段的第二个执行的过滤器。

该过滤器只对请求上下文存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。

而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护。

知道配置类似zuul.routes.user-service.url=http://localhost:8080/这样的底层都是通过httpclient直接发送请求的,也就知道为什么这样的情况没有做到负载均衡的原因所在。

8.SendForwardFilter

它的执行顺序是500,是route阶段第三个执行的过滤器。该过滤器只对请求上下文中存在的forward.do参数进行处理请求,即用来处理路由规则中的forward本地跳转装配。

后置过滤器

9.SendErrorFilter

它的执行顺序是0,是post阶段的第一个执行的过滤器。该过滤器仅在请求上下文中包含error.status_code参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。而该过滤器的具体逻辑就是利用上下文中的错误信息来组成一个forward到api网关/error错误端点的请求来产生错误响应。

10.SendResponseFilter

它的执行顺序为1000,是post阶段最后执行的过滤器,该过滤器会检查请求上下文中是否包含请求响应相关的头信息,响应数据流或是响应体,只有在包含它们其中一个的时候执行处理逻辑。

而该过滤器的处理逻辑就是利用上下文的响应信息来组织需要发送回客户端的响应内容。

如何禁用过滤器

Spring Cloud默认为Zuul编写并启用了一些过滤器,一些场景下,想要禁用掉部分过滤器,此时该怎么办呢?

只需设置zuul.<SimpleClassName>.<filterType>.disable=true,即可禁用SimpleClassName所对应的过滤器。

以过滤器org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter为例,只需设置zuul.SendResponseFilter.post.disable=true,即可禁用该过滤器。

Zuul自定义过滤器

由上文可知,我们可以将Zuul的过滤器分为pre,route,post三大类型。本文我们将自定义一个pre类型的过滤器。

首先要自定义一个过滤器,只需要完成以下几个步骤。

1.继承ZuulFilter

2.指定过滤类型、过滤顺序

3.是否执行这个过滤器、过滤内容

1.首先定一个抽象类 AbstractZuulFilter.java 继承ZuulFilter

2.定义preFilter的抽象类,继承AbstractZuulFilter。指定过滤器类型为pre类型。

3.接着编写具体一个具体的限流过滤器

4.最后创建一个配置类,将下列过滤器托管给spring。​​​​​​​

Zuul 容错与回退

看过之前讲解什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前的文章的同学们应该都知道,很多时候,无论是因为服务节点的重启,宕机还是由于网络故障,我们在访问某一个服务节点时可能会出现阻塞或者异常。

Zuul进行路由时候也会因为这些原因出现异常。

此时如果我们直接将异常信息展示给用户的话肯定是很不友好的,我们需要展示给用户的是用户能看的明白的造成访问失败的原因。

这里我们就可以用到Zuul的回退处理了。

SpringCloud中使用Hystrix实现微服务的容错与回退,其实Zuul默认已经整合了Hystrix。

实现Zuul添加回退

要实现Zuul添加回退,需要实现ZuulFallbackProvider接口,然后在实现类中,指定为哪个微服务提供回退,并提供一个ClientHttpResponse作为回退响应。​​​​​​​

注意:在Spring cloud Edgware版本之前,要想回退,需实现ZuulFallBackProvider接口,从Spring cloud Edgware版本之后,实现FallbackProvider接口。

总结

本文从什么是网关和为什么要使用网关引入SpringCloud Zuul,介绍了什么是Zuul,Zuul的使用场景。并且分析了Zuul的核心架构以及Zuul请求处理的生命周期。然后着重的讲解了Zuul的路由和过滤器这两个在任何网关中都十分重要的组件。本文旨在带领大家对Zuul有一个全局的认识。在日后学习其它网关技术的时候也能够融会贯通,学习起来能够事半功倍。

原创不易,如果大家喜欢,赏个分享点赞在看三连吧。和大家一起成为这世界上最优秀的人。