一、Zuul简介
1.1、Zuul是什么
Zuul是Netflix开源的API网关。
API网关:类似于面向对象设计模式中的Facade模式,它存在就像是整个微服务系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤,它除了要实现请求路由、负载均衡、来源合法性检测、权限校验、反爬虫等功能之外,还需要更多能力,比如与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列的高级功能。
1.2、Zuul能干什么
1.2.1、身份验证和安全
识别每个请求的身份验证,并拒绝不满足的请求。
1.2.2、审查和监测
跟踪边缘的有意义数据和统计数据,以便我们准确地了解系统及业务运行情况。
1.2.3、动态路由
根据需要,将请求动态路由到不同的后端集群。
1.2.4、压力测试
逐渐增加到集群的流量,以衡量性能。
1.2.5、负载分配
为每种类型的请求分配容量,并删除超出限制的请求。
1.2.6、静态响应处理
直接在边缘构建一些响应,而不是将他们转发到内部集群。
1.3、Zuul有什么
Zuul包含了对请求的路由和过滤这两个最主要的功能:
1.3.1、路由
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。
1.3.2、过滤
过滤功能则负责对请求的处理过程进行干预,是实现请求验证、服务聚合等功能的基础。
实际上,路由功能在真正运行时,它的路由映射和请求转发都是由几个不同的过滤器完成的。其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是有route类型的过滤器来完成,对pre类型的过滤器获得路由地址并进行转发。
二、Zuul的Hello World
2.1、加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
2.2、加入配置
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
server.port=8780
spring.application.name=service-zuul
zuul.routes.api-a.path=/aa/**
zuul.routes.api-a.serviceId=userservice
zuul.routes.api-b.path=/bb/**
zuul.routes.api-b.serviceId=service-feign
注意:serviceId需要全部小写
2.3、启动类加入注解
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
说明:
1、@EnableEuulProxy:开启Zuul服务,将本地调用转发到响应的服务,Zuul使用Ribbon来定位一个要转发的服务实例,并且所有请求都以hystrix命令执行。
2、注意: @EnableZuulProxy不包括发现服务的客户端,因此对于基于服务ID的理由,还需要提供服务治理(例如Eureka)
3、使用@EnableZuulServer也可以运行不带代理的Zuul服务器,差别是它不会自动添加任何代理过滤器,也没有服务发现和代理。
2.4、测试运行
1、如果配置文件里没有配置zuul的话,zuul会把Eureka Server里面的服务都读取出来,此时运行的时候,需要用各个服务的serviceId作为ContextPath。
2、如果配置了zuul,还可以用配置的前缀代替服务的serviceId,然后进行访问。
3、如果不希望直接使用serviceId进行访问,可以配置ignored.services: ‘*’,例如:
zuul.ignored.services='*'
当然可以不用*, 而是直接把要禁用的serviceId罗列出来,用逗号分开。
三、路由配置
3.1、传统路由配置
所谓的传统路由配置,就是在不依赖于服务发现机制的情况下,通过在配置文件中具体制定每个路由表达式实例的映射关系来实现API网关对外部请求的路由。
3.3.1、单实例配置
通过zuul.routes.<route>.path与zuul.routes.<route>.url参数的方式进行配置,如:
zuul.routes.dd.path=/dd/**
zuul.routes.dd.url=http://localhost:8769
3.3.2、多实例配置
通过zuul.routes.<route>.path与zuul.routes.<routes>.serviceId参数的方式进行配置,结合Ribbon来做负载均衡,且让Ribbon关闭和Eureka的结合,比如:
zuul.routes.dd.path=/dd/**
#zuul.routes.dd.url=http://lcoalhost:8769
zuul.routes.dd.serviceId=ddservice
ribbon.eureka.enabled=false
ddservice.ribbon.listOfServers=http://localhost:8769,http://localhost:8768
这种简单的URL路由不会被执行为HystrixCommand,也不能使用Ribbon对多个URL进行负载均衡。
3.2、服务路由配置
Spring Cloud Zuul整合了Spring Cloud Eureka,实现了对服务实例的自动化维护,那么使用服务路由配置的时候,不需要向传统路由配置方式那样为serviceId指定具体服务实例地址,值需要通过zuul.routes.<route>.path与zuul.routes.<route>.serviceId参数对的方式,进行配置即可。
还可以简化配置,直接设置<route>为具体的serviceId,例如:
zuul.routes.service.feign.path=/dd/**
请记得开启前面实例中关闭的Ribbon和Eureka的结合。
3.2.1、服务路由的默认规则
大部分路由规则机会都会采用服务名作为外部请求的前缀,zuul已经自动帮我们实现了以服务名作为前缀的映射,不需要去配置它。
如果不希望外部能直接访问,可以使用zuul.ignored.services参数来设置一个服务名匹配表达式来定义不自动创建路由的规则。
3.2.2、自定义路由映射关系
有时候,为了兼容外部不同版本的客户端程序,我们需要在微服务接口上定义版本的标记,比如:service-v1, service-v2,而这一在映射的时候不太好管理。
通常的做法是,为这些不同版本的微服务应用,生成以版本号为路由前缀定义规则的路由规则,比如/v1/userservice, 具体示例入下:
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
return new PatternServiceRouteMapper("(?<name>^.+)-
(?<version>v.+$)","${version}/${name}");
}
注意:当存在多个匹配的路由规则时,匹配结果完全取决于路由规则的定义顺序。由于properties的配置内容无法保证有序,所以当出现这样的情况时,为了保证路由的优先顺序,需要使用yml文件来配置,以实现有序的路由规则。
3.2.3、Cookie与头信息
默认情况下,spring cloud zuul在请求路由时,会过滤掉http请求头信息中一些敏感信息,防止他们被传递到下游的外部服务器。
默认敏感的头部信息通过zuul.sensitiveHeaders参数定义,默认包括cookie, set-Sookie, authorization三个属性。
这会引发一个场景的问题,使用了spring security, shiro等安全框架构建的web应用,通过spring cloud zuul构建网关来进行路由时,由于cookie信息无法传递,web应用将无法实现登陆和鉴权。为了解决这个问题,配置的方法可以为:
1)通过设置全局参数来覆盖默认值,如:
zuul.sensitiveHeaders=
不推荐这种做法,范围太大
2)将制定路由的敏感头设置为空,如:
zuul.routes.userservice.sensitiveHeaders=
3.2.4、重定向
使用API网关的一个重要原因就是将网关作为统一入口,从而不暴露所有内部服务细节。但我们在应用内部调整的URL却是具体的web应用实例的地址,而不是通过网关的路由地址,该问题的根本原因在于spring cloud zuul在路由请求时,并没有将最初的host信息设置正确,该怎么办呢?
配置zuul.add-host-header=true即可。
3.2.5、路由回退
3.2.5.1、Hystrix和Ribbon支持
spring cloud zuul默认集成了hystrix和Ribbon,自然就拥有线程隔离和断路器,以及对服务调用的客户端负载均衡功能。
需要注意的是,当使用path与url的映射关系来配置路由规则的时候,对于路由转发的请求不会采用hystrixCommand来包装,所以这类请求没有线程隔离和断路器的保护,并且也不会有负载均衡的能力。因此,我们在使用zuul的时候,尽量使用path与serviceId的组合来进行配置,这样不仅可以保证API网关的健壮与稳定,也能用到ribbon的客户端负载均衡功能。
所有路由的默认hystrix隔离模式(ExecutionsolationStrategy)为SUMAPHORE。
3.2.5.2、Hystrix的路由回退
可以通过创建ZuulFallbackProvider类型的bean来提供回退响应。
zuul首先会去判断是否存在自定义的zuulFallbackProvider,如果有,那么直接回调你自定义实现类的fallbackResponse(), 但是这个方法是无参的,也就是说此时虽然你能够给调用端返回一个消息,但此时你并不知道发送了什么样的异常(也就是说这里你是获取不到异常信息的)
3.2.6、请求路由其他配置
3.2.6.1、路由前缀
zuul.prefix:为路由规则增加前缀,例如:zuul.prefix=/user
zuul.strip.prefix(默认为true):这个设置是在转发请求之前,从请求中删除代理前缀
3.2.6.2、本地跳转
通过在serviceId中使用forward来指定需要跳转的服务器资源路径,例如:
zuul.routes.api-t.path=/tt/**
zuul.routes.api-t.serviceId=forward:/tt
3.2.6.3、忽略表达式
zuul.ignored.patterns:设置不希望被API网关进行路由的url表达式。例如:
ignoredPatterns: //products/
注意:它的范围并不是真毒某个路由,而是对所有路由,所以要小心使用。
3.2.6.4、路由端点
使用@EnableZuulProxy,将启用/routes端点,可返回映射路由的列表。
可以通过将endpoints.routes.enabled设置为false来禁用此端点。
配置属性zuul.max.host.connections已被两个新属性zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections替换,分别默认为200和20
3.2.6.5、性能优化
zuul内部路由可以理解为使用一个线程池去发送路由请求,所以我们也需要扩大这个线程池的容量,如:
zuul.host.max-per-route-connections=1000
zuul.host.max-total-connections=1000
四、请求过滤器
4.1、请求过滤器概述
加入zuul做网关服务后,外部客户端访问我们的系统已经有了统一的入口,那么那些与具体的业务无关的功能,应该在请求到达的时候就完成,比如:校验和过滤,这就涉及到了zuul的另一个主要功能:请求过滤。
定义一个过滤器
把这个过滤器加入spring容器并使用。
4.2、请求过滤器的高层架构
4.2.1、ZuulServlet
zuul的核心是一系列的filters,zuul大部分功能都是通过过滤器来实现的。
1、zuulServlet是zuul的核心类,用来调度不同阶段的filters,处理请求,并处理异常等,路径是/zuul,可以使用zuul.servlet-path属性更改此路径。
2、功能类似于spring mvc中的DispatcherServlet,所有的Request都要经过它的处理。
3、里面有三个核心方法:preRoute(), route(), postRoute()
4、ZuulServlet会把具体的执行交给ZuulRunner去做,ZuulServlet是单例,因此ZuulRunner也仅有一个实例。
5、Zuul的过滤器直接没有直接的相互通信,他们之间通过一个RequestConext的静态类来进行数据传递。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据,ZuulRequest会初始化RequestContext。
6、ZuulRunner直接将执行逻辑交给FilterProcessor处理,FilterProcesser也是单例,其功能就是根据filterType执行filter的处理逻辑,大致过程如下:
1)根据Type获取所有输入该Type的filter。
2)遍历执行每个filter的处理逻辑,processZuulFilter(ZuulFilter filter)
3)RequestContext对每个filter的执行状况进行记录,如果执行失败,则对异常封装后抛出。
4.2.2、请求生命周期
4.2.2.1、过滤器类型与生命周期
PRE:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用 Apache HttpClient或Netflix Ribbon请求微服务。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
ERROR:在其他阶段发生错误时,执行该过滤器。
一般来说,如果需要在请求到达后端应用前,就进行处理的话,会选择前置过滤器,例如鉴权、请求转发、增加请求参数等行为。在请求完成后,需要处理的操作放在后置过滤器中完成,例如统计返回值和调用时间、记录日志、增加跨域头等行为。
路由过滤器一般只需要选择zuul中内置的即可,错误过滤器一般只需要一个,这样可以在Gateway遇到错误逻辑时,直接抛出异常中断流程,并直接统一处理返回结果。
4.3、核心过滤器
4.3.1、概述
在spring cloud zuul中,为了让API网关组件可以被更方便地使用,它在HTTP请求声明周期的各个阶段默认实现了一批核心过滤器,他们会在API网关服务启动的时候,被自动加载和启动。
默认spring-cloud-netflix-core模块org.springframework.cloud.netflix.zuul.filters包下。
4.3.2、pre过滤器
1、ServletDetectionFilter
用来检测当前请求是通过spring的DispatcherServlet处理运行的,还是通过ZuulServlet来处理运行的。它的检测结果会议布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样后续的过滤器中,可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法来判定请求处理的源头,以实现后续不同的处理机制。
一般情况下,发送到API网关的外部请求都会被spring的DispatcherServlet处理,除了通过/zuul/*路径访问的请求会绕过DispatcherServlet,被ZuulServlet处理。
2、FormBodyWrapperFilter
解析表单数据,并对下游请求进行重新编码。
该过滤器仅对两类请求生效,第一类是Context-Type为appliction/x-www-form-urlencoded的请求,第二类是Context-Type为multipart/form-data并且是由spring的DispatcherServlet处理的请求,而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象。
3、DebugFilter
该过滤器会根据配置参数zuul.debug.request和请求中的debug参数来决定是否执行过滤器中的操作。
它的具体操作内容是将当前请求上下文中的debugRouting和debugRequest参数设置为true。由于在同一个请求的不同声明周期都可以访问到这两个值,所以我们在后续的各个过滤器中可以利用这两个值来定义一些debug信息,这样当线上环境出现问题的时候,可以通过参数的方式来激活这些debug信息以帮助分析问题,另外,对于请求参数中的debug参数,我们可以通过zuul.debug.parameter来进行自定义。
4、PreDecorationFilter
此过滤器根据提供的RouteLocator确定在哪里和如何路由。
该过滤器会判断当前请求上下文中是否存在forward.do和serviceId参数,如果都不存在,那么他就执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为第二个信息就是根据当前请求的路由信息加载尽量的)。
另外,还可以在该实心中对http头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如X-Forwarded-Host, X-Forwarded-Port。对于这些头域,是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值是true,所以zuul在请求调整时会默认为请求增加X-Forward-*头域,包括X-Forwarded-Host, X-Forwarded-Post, X-Forwarded-For, X-Forwarded-Prefix, X-Forwarded-Proto。也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作。
4.3.3、route过滤器
1、RibbonRoutingFilter
该过滤器值对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。
该过滤器的执行逻辑就是面向服务路由的核心,它通过使用ribbon和hystrix来向服务实例发起请求,并将服务实例的请求结果返回。
2、SimpleHostRoutingFilter
该过滤器只对请求上下文存在routeHost参数的请求进行处理,即只通过URL配置路由规则的请求生效。
该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求时直接通过httpclient包实现的,而没有使用hystrix命令进行包装,所以这类请求并没有线程隔离和断路器保护。
3、SendForwardFilter
该过滤器只对请求上下文中存在forward.do参数进行处理请求,即用来处理路由规则中的forward本地跳转装配。
4.3.4、post过滤器
1、SendErrorFilter
该过滤器仅在请求上下文中包含error.status_cdoe参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。
该过滤器的具体执行逻辑就是利用上下文中的错误信息来组成一个forward到API网关/error错误断掉的请求来产生错误响应。
可以通过error.path属性来更改默认转发路径(/error).
2、SendResponseFilter
该过滤器会检测请求上下文中是否包含请求响应相关的头信息,响应数据流或是响应体,只有在包含他们其中一个的时候执行处理逻辑。
其处理逻辑就是利用上下文的响应信息来阻止需要发送回客户端的响应内容。
4.3.5、禁用过滤器
1、重写shouldFilter逻辑,让他返回false
2、通过配置来禁用:
zuul.<SimpleClassName>.<filterType>.disable=true
<SimpleClassName>代表过滤器的类名,<filterType>代表过滤器类型。
4.4、异常处理
过于过滤器的处理流程中发生异常,通常的处理方式:
1、使用try-catch,在每个过滤器当中进行处理,然后转抛出ZuulRuntimeException,异常会到达SendErrorFilter里面进行处理。
2、可以自己写一个类来继承SendErrorFilter,在里面进行处理。
4.5、重试机制
1、zuul的重试机制是依赖Spring-Retry的,因此pom.xml必须有spring-retry:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
2、开启重试,也可以具体的为某个服务开启重试
zuul.routes.user-api.retryable=true
其中的user-api是路由名称,可自行自定义。
3、然后是相应的hystrix和ribbon配置:
hystrix.command.default.execution.timeout.enabled=true hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
ribbon.ConnectTimeout=250 ribbon.ReadTimeout=1000 ribbon.OkToRetryOnAllOperations=true ribbon.MaxAutoRetries=0 ribbon.MaxAutoRetriesNextServer=1
4、关闭重试机制:
全局关闭:zuul.retryable=false
指定路由关闭:zuul.routes.<route>.retryable=false
五、饿汉式加载
zuul内部使用Ribbon调用远程的URL,并且Ribbon客户端默认在第一次调用时由spring cloud加载,可以使用以下配置更改zuul的此行为:
zuul.ribbon.eager.load.enabled=true
在spring cloud zuul的饥饿加载中,没有涉及专门的参数来配置,而直接采用了读取路由配置来进行饥饿加载的做法。所以,如果我们使用默认路由,而没有通过配置的方式指定具体的路由规则,那么zuul.ribbon.eager.load.enabled=true的设置就没有什么作用了。
因此,在真正使用的时候,可以通过zuul.ignored-services=*来忽略所有的默认路由,让所有路由配置均维护在配置文件中,以达到网关启动的时候就默认初始化好各个路由转发的拒载均衡对象。
六、上传文件
对于小文件,zuul不用做任何特别配置,直接按照路径进行路由就可以了;
对于大文件,一个简单的方案就是使用在你的路径前添加上:/zuul/,来绕开spring的DispatcherServlet,以避免多部分处理,同时也避免了后台提取中文名乱码的问题。
1、要设置真正处理文件上传的应用,设置允许大文件上传,默认最大是10M,如:
spring.http.multipart.enabled=true
spring.http.multipart.max-file-size=1000Mb
spring.http.multipart.max-request-size=1500Mb
2、要设置zuul应用,注意是超时的问题,如:
zuul.host.socket-timeout-millis=10000
zull.host.connect-timeout-millis=10000
hystrix.command.default.execution.isolation.thread.timeoutMilliseconds=15000
ribbon.ConnectTimeout=500
ribbon.ReadTimeout=15000
七、健康检查
1、加入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、配置示例
# 开启健康检测(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enable=true
# 租期到期时间,默认90秒
eureka.instance.lease-expiration-duration-in-senconds=30
# 租赁更新时间间隔,默认30,即30秒发送一次心跳
eureka.instance.lease-renewal-interval-in-seconds=10
#hystrix dashboard的信息收集频率,默认500毫秒,设置dashboard的刷新频率
hystrix.stream.dashboard.intervalInMillseconds=5000