什么是网关
- Zuul是Netflix开源的微服务网关,它可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul的核心是一系列的过滤器
- Zuul网关也有负载均衡的功能,该负载均衡是服务端的负载均衡,因为客户访问网关,由服务网关来决定跳到哪一个服务,而不是自己决定的。
一:主要功能
- 身份认证与安全:识别每一个资源的验证要求,并且拒绝那些与要求不符合的请求
- 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而为我们带来精确的生产视图
- 动态路由:动态的将请求路由到不同的后端集群
- 压力测试:逐渐增加指向集群的流量,以了解性能
- 为每一种负载类型分配对应的容量,并且启用超出限定值的请求
为什么需要zuul
- 不同的微服务一般会有不同的网络地址,而外部客户端(例如手机APP)可能需要调用多个服务接口才能完成一个业务需求。
一:没有使用网关服务的时候
- 缺点:
(1)客户端会多次请求不同的微服务,增加了客户端的复杂性
(2)存在跨域请求,在一定场景下处理相对复杂
(3)认证复杂,每个服务都需要独立认证
二: 使用了服务网关的优点
- 优点:
(1)易于监控。可以在微服务网关收集监控数据并将其推送到外部系统进行分析
(2)易于认证。可以在微服务网关上进行认证,然后再将请求转发到后端的微服务,没有不要在每个微服务中进行认证
(3)减少了客户端与各个微服务之间的交互次数。
三:搭建服务端和Eureka的Server
- 这里我们省略搭建步骤,因为前面几篇中,我们已经搭建成功,这里直接看下Eureka的服务注册中心
四:如何快速的搭建网关
- 加入maven依赖
<!--Eureka的客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--zuul的网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- yml配置文件
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
- 主启动类上面加上注解
package com.xiyou.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author 92823
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class MyZuulApplication {
public static void main(String[] args) {
SpringApplication.run(MyZuulApplication.class, args);
}
}
- 我们通过网关地址来进行访问:
(1)http://localhost:9001/my-provider/test
(2)其中localhost:9001是网关的IP和端口,my-provider是下游服务中的注册到Eureka服务上的名字,test是其具体的映射url
五:zuul网关核心配置详解
- 关闭通过微服务名称访问并设定其他映射
- 这里我们通过网关访问,也需要用到真实的注册到Eureka服务器中的服务名,所以,为了不暴露真实服务名,我们将其关闭,并且给其一个配置,让客户可以通过配置的映射,映射到真实的Eureka服务名,并进行路由
(1)关闭所有的微服务名称
# 关闭所有的微服务名称,不能通过Eureka注册的服务名称对其进行访问 * 表示所有
zuul:
ignored-services: "*"
(2)关闭部分的微服务名称,若要关闭多个,用,号隔开
# 关闭部分配置的微服务名称,不能通过Eureka注册的名称对其进行访问 写多个服务名
zuul:
ignored-services: my-provider, my-provider-1
(3)指定自己的映射,通过自己的配置,映射到具体的微服务(方式一)
# 测试一
# 关闭所有的微服务名称访问,只能由指定的微服务名称进行访问
# http://localhost:9001/my-provide/test/3 无法访问
# http://localhost:9001/my-provide-1/test/3 无法访问
# http://localhost:9001/test-provider/test/3 可以访问
# http://localhost:9001/test-provider-2/test/3 可以访问
zuul:
ignored-services: "*"
routes:
# 这个名字随便写
my-suibian-provider-1:
# 由test-provider映射到my-provider
path: /test-provider/**
serviceId: my-provider
# 这个名字随便写
my-suibian-provider-2:
# 由test-provider-2映射到my-provider-1
path: /test-provider-2/**
serviceId: my-provider-1
(4)指定自己的映射(方式二)
# 测试二
# 利用服务名: 映射名进行路径映射
# 结果和测试结果一一致
zuul:
ignored-services: "*"
routes:
# 其中my-provider是服务注册在eureka的名称 后面的是映射的,就是浏览器中的地址
my-provider: /test-provider/**
my-provider-1: /test-provider-2/**
(5)测试的yml(完整的yml测试文件)
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 关闭所有的微服务名称,不能通过Eureka注册的服务名称对其进行访问 * 表示所有
# zuul:
# ignored-services: "*"
# 关闭部分配置的微服务名称,不能通过Eureka注册的名称对其进行访问 写多个服务名
# zuul:
# ignored-services: my-provider, my-provider-1
# 测试一
# 关闭所有的微服务名称访问,只能由指定的微服务名称进行访问
# http://localhost:9001/my-provide/test/3 无法访问
# http://localhost:9001/my-provide-1/test/3 无法访问
# http://localhost:9001/test-provider/test/3 可以访问
# http://localhost:9001/test-provider-2/test/3 可以访问
# zuul:
# ignored-services: "*"
# routes:
# # 这个名字随便写
# my-suibian-provider-1:
# # 由test-provider映射到my-provider
# path: /test-provider/**
# serviceId: my-provider
# # 这个名字随便写
# my-suibian-provider-2:
# # 由test-provider-2映射到my-provider-1
# path: /test-provider-2/**
# serviceId: my-provider-1
# 测试二
# 利用服务名: 映射名进行路径映射
# 结果和测试结果一一致
zuul:
ignored-services: "*"
routes:
# 其中my-provider是服务注册在eureka的名称 后面的是映射的,就是浏览器中的地址
my-provider: /test-provider/**
my-provider-1: /test-provider-2/**
- 指定url和path
- 这里我们直接指定了url和path,这里的url是具体的IP+端口号,所以这个配置会破坏掉Ribbon的负载均衡等特性
(1)yml文件
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 通过path和url指定具体的url(ip+端口号), 这样子会破坏掉ribbon的负载均衡
# 这里的url后面跟的路径不可以写多个url
# 测试路径: http://localhost:9001/test-url/test/3
zuul:
routes:
# 随便写
suibian-write:
path: /test-url/**
# 这个url对应的是my-provider-3
url: http://localhost:8005
(2)测试路径
http://localhost:9001/test-url/test/3
- 本地维护一个服务注册列表,同时指定path和URL,不去破坏负载均衡
- 我们配置的时候需要将ribbon关闭,因为本地维护了一个服务注册列表
- 具体看代码注释
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 地址http://localhost:9001/test-ribbon/test/3
# 关闭ribbon,否则下面的local-ribbon会报错,因为我们这里试验的是不在Eureka中获取
ribbon:
eureka:
enabled: false
# 同时指定url和path,这里手动指定了本地的服务列表,不会破坏ribbon的负载均衡
zuul:
routes:
# 随便写一个名字
suibian-write:
path: /test-ribbon/**
# 这个也是随便写的,因为不走Eureka
serviceId: local-ribbon
# 设置本地的服务列表,其中local-ribbon对应的是上面的serviceID
local-ribbon:
ribbon:
listOfServers: http://localhost:8003/test, http://localhost:8004/test, http://localhost:8005/test
(2)测试地址
http://localhost:9001/test-ribbon/test/3
- 设置路由前缀并判断是否剥离
- 这里的剥离指的是发送到zuul之后,zuul转发给真实的服务器的时候,若是true则进行剥离,将请求处理之后,再从zuul发送给真实的服务器,若是false,则转发到目标真实服务器的时候,zuul接受到的请求是什么,下游服务器就是什么,不进行剥离。
- 注意若我们没有进行剥离,需要在真实服务器中加上context-path配置项,指定其未剥离的路径
(1)zuul网关的yml配置
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 转发前去掉所有的前缀
# 意思就是路由接受到之后去掉前缀,再转发给后面的下游服务
# 默认就是true,表示剔除前缀
zuul:
strip-prefix: false
# 默认的前缀 用的是下游服务的context-path
prefix: /all
routes:
# 随便写的名字
suibian-write-1:
path: /provider-1/**
serviceId: my-provider
# 局部不剔除 若全局不剔除,局部不剔除,则下游服务器的context-path必须写/全局/局部(这里的局部就是path)
stripPrefix: false
suibian-write-2:
path: /provider-2/**
serviceId: my-provider-1
# 剔除局部的,则下游的context-path只用加上/all就行
stripPrefix: true
- 参考注释可以看到:
(1.1)我们将全局的是否剥离配置成false,且前缀新增/all,即每个请求过来都需要在真实服务器中新增context-path=/all
(1.2)serviceId是my-provider的局部剔除关闭,所以在全局前缀后面,还应该增加其局部的前缀,即/all//provider-1
server:
port: 8004
# 为了应对zuul中配置的前缀
servlet:
context-path: /all/provider-1
# 注册到eureka服务端的服务名称
spring:
application:
name: my-provider
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-provider-2-8004
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
(1.3)serviceId是my-provider-1的服务局部剔除开启,所以我们会剔除掉,增加的局部前缀,所以只需要在context-path中新增/all即可。
server:
port: 8003
# 为了应对zuul中配置的前缀
servlet:
context-path: /all
# 注册到eureka服务端的服务名称
spring:
application:
name: my-provider-1
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-provider-1-8003
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
- 过滤敏感路径
- 满足敏感路径的url不会进行发送,返回404
(1)yml文件
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 忽略某些路径 不对其进行路由操作(全局配置)
# http://localhost:9001/test-admin/test/3 无法访问 404
zuul:
ignored-patterns: /**/test/**
routes:
# 随便写
suibian-write:
path: /test-admin/**
serviceId: my-provider
- 如上面配置,url中带有test关键字的,我们都不会进行路由
(2)测试路径
http://localhost:9001/test-admin/test/3
- 设置敏感头相关信息
- 设置敏感头信息,表示的是我们访问的网关的请求如果没有设置的话,默认情况下Cookie,Set-Cookie,Authorization这几个请求头信息是不会进行传递的,无法转发到下游服务器,若我们想将这些信息路由到真实的服务器,就需要敏感头设置
- sensitive-headers这个配置就是敏感头的相关配置,若什么都不写(sensitive-headers=),表示的是全部放开,不会拦截任何敏感头,若写了相关的值(sensitive-headers:Cookie),表示的是Cookie就会被拦截,不会转发到下游真实服务器
(1)yml配置
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# sensitive-headers: 用来放开敏感头数据 默认是Cookie Set-Cookie Authorization不往下传递
# sensitive-headers: 什么都不写,则表示不会过滤掉Cookie这些字段
# http://localhost:9001/test-sen-2/testCookie 该请求携带cookie 可以正常访问
# http://localhost:9001/test-sen-1/testCookie 该请求携带Cookie 不能正常访问 因为Cookie被禁止了
zuul:
routes:
# 随便写
suibian-write-1:
path: /test-sen-1/**
serviceId: my-provider
# 随便写
suibian-write-2:
path: /test-sen-2/**
serviceId: my-provider-1
# 开启敏感头
sensitiveHeaders:
# 全局敏感头,这样配置就是Cookie不向下传递
sensitive-headers: Cookie
可以参考上面的注释,这里我们在全局拦截了Cookie不让其向下传递,又在my-prvider-1的服务局部配置中设置敏感头,让其全部可以进行传递,所以此时访问my-provider-1的服务是可以传递Cookie的,其他的不行。
- 这里可以参考这个博客(非本人)这是地址
zuul的过滤器详解
- zuul其实就是一堆过滤器
一:zuul过滤器类型与请求声明周期
zuul过滤器的类型
- PRE类型:
(1)该过滤器在请求被路由之前进行调用,我们可以利用这种过滤器实现身份的验证,在集群中选择请求的微服务,记录调试信息等
(2)常用的PRE类型过滤器
- ServletDetectionFilter:它的执行顺序为-3
- 是最先被执行的过滤器。该过滤器总是会被执行,主要用来检测当前请求是通过Spring的DispatcherServlet处理运行,还是通过ZuulServlet来处理运行的。
- 它的检测结果会以布尔类型保存在当前请求上下文的isDispatcherServletRequest参数中,这样在后续的过滤器中,我们就可以通过RequestUtils.isDispatcherServletRequest()和RequestUtils.isZuulServletRequest()方法判断它以实现做不同的处理。
- 一般情况下,发送到API网关的外部请求都会被Spring的DispatcherServlet处理,除了通过/zuul/路径访问的请求会绕过DispatcherServlet,被ZuulServlet处理,主要用来应对处理大文件上传的情况。另外,对于ZuulServlet的访问路径/zuul/,我们可以通过zuul.servletPath参数来进行修改
- Servlet30WrapperFilter:执行顺序是-2
- 是第二个执行的过滤器。目前的实现会对所有请求生效,主要为了将原始的HttpServletRequest包装成Servlet30RequestWrapper对象
- FormBodyWrapperFilter 它的执行顺序为-1
- 该过滤器仅对两种类请求生效,第一类是Content-Type为application/x-www-form-urlencoded的请求,第二类是Content-Type为multipart/form-data并且是由Spring的DispatcherServlet处理的请求(用到了ServletDetectionFilter的处理结果
- 而该过滤器的主要目的是将符合要求的请求体包装成FormBodyRequestWrapper对象
- PreDecorationFilter 他的执行顺序是5
- 是pre阶段最后被执行的过滤器。该过滤器会判断当前请求上下文中是否存在forward.to和serviceId参数,如果都不存在,那么它就会执行具体过滤器的操作(如果有一个存在的话,说明当前请求已经被处理过了,因为这两个信息就是根据当前请求的路由信息加载进来的)。
- 而它的具体操作内容就是为当前请求做一些预处理,比如:进行路由规则的匹配、在请求上下文中设置该请求的基本信息以及将路由匹配结果等一些设置信息等,这些信息将是后续过滤器进行处理的重要依据,我们可以通过RequestContext.getCurrentContext()来访问这些 信息。
- 另外,我们还可以在该实现中找到一些对HTTP头请求进行处理的逻辑,其中包含了一些耳熟能详的头域,比如:X-Forwarded-Host、X-Forwarded-Port。
- 另外,对于这些头域的记录是通过zuul.addProxyHeaders参数进行控制的,而这个参数默认值为true,所以Zuul在请求跳转时默认地会为请求增加X-Forwarded-*头域,包括:X-Forwarded-Host、X-Forwarded-Port、X-Forwarded-For、X-Forwarded-Prefix、X-Forwarded- Proto。
- 我们也可以通过设置zuul.addProxyHeaders=false关闭对这些头域的添加动作
- ROUTING类型:
(1)这种过滤器将请求路由到具体的微服务。这种过滤器用于构建发送给微服务的请求,并使用ApacheHttpClient或者Netfilix Ribbon请求微服务
(2)常用的ROUTING类型的过滤器:
- RibbonRoutingFilter:它的执行顺序为10,是route阶段第一个执行的过滤器
- 该过滤器只对请求上下文中存在serviceId参数的请求进行处理,即只对通过serviceId配置路由规则的请求生效。而该过滤器的执行逻辑就是面向服务路由的核心,它通过使用Ribbon和Hystrix来向服务实例发起请求,并将服务实例的请求结果返回
- SimpleHostRoutingFilter:它的执行顺序为100
- 是route阶段第二个执行的过滤器。该过滤器只对请求上下文中存在routeHost参数的请求进行处理,即只对通过url配置路由规则的请求生效。而该过滤器的执行逻辑就是直接向routeHost参数的物理地址发起请求,从源码中我们可以知道该请求是直接通过httpclient包实现的,而没有使用Hystrix命令进行包装,所以这类请求并没有线程隔离和断路器的保护
- SendForwardFilter:它的执行顺序为500,是route阶段第三个执行的过滤器
- 该过滤器只对请求上下文中存在forward.to参数的请求进行处理,即用来处理路由规则中的forward本地跳转配置
- POST类型:
(1)这种过滤器在路由到微服务以后执行。这种过滤器可以用来为响应添加标志的Http Header,收集统计信息和指标,将响应从微服务发送给客户端
(2)常用的POST类型的过滤器:
- SendResponseFilter:它的执行顺序为1000
- 是post阶段最后执行的过滤器。该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,只有在包含它们其中一个的时候就会执行处理逻辑。而该过滤器的处理逻辑就是利用请求上下文的响应信息来组织需要发送回客户端的响应内容
- ERROR类型:
(1)在PRE,ROUTING,POST阶段发生错误的时候,执行这个过滤器
(2)常用的ERROR类型的过滤器:
- SendErrorFilter:它的执行顺序为0
- 该过滤器仅在请求上下文中包含error.status_code参数(由之前执行的过滤器设置的错误编码)并且还没有被该过滤器处理过的时候执行。而该过滤器的具体逻辑就是利用请求上下文中的错误信息来组织成一个forward到API网关/error错误端点的请求来产生错误响应
zuul过滤器的执行顺序
- 我们的过滤器,先执行PRE,ROUTING,POST,然后再在同类型的过滤器按照其各自的order(顺序)大小执行,越小的就越先被执行,order的值可以为负数
zuul的声明周期
二:如何编写一个Zuul的过滤器
- 这里我们简单编写一个zuul的服务器
- yml文件
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
- 主启动类
package com.xiyou.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author 92823
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class MyZuulApplication {
public static void main(String[] args) {
SpringApplication.run(MyZuulApplication.class, args);
}
}
- 自定义过滤器
package com.xiyou.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
/**
* @author 92823
* 自定义Zuul的过滤器
*/
@Component
public class MyPreFilter extends ZuulFilter {
/**
* 过滤器的类型,这里是pre前置过滤器
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的顺序 数字越小,越优先执行,可以是负数
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否过滤,当返回是True的时候,正常执行run方法,开启过滤
* @return
*/
@Override
public boolean shouldFilter() {
// 这里直接让其正常过滤
return true;
}
/**
* 过滤器的过滤方法
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
System.out.println("开始执行自定义的过滤器......");
// 得到当前的容器
RequestContext currentContext = RequestContext.getCurrentContext();
// 得到当前的request
HttpServletRequest request = currentContext.getRequest();
// 得到tooken
String tooken = request.getHeader("Token");
if (StringUtils.isEmpty(tooken)) {
// 封装失败的HTTP响应
shouldNotFilter(currentContext);
return null;
} else {
// 进行路由,传递true
currentContext.setSendZuulResponse(true);
// 设置响应码
currentContext.setResponseStatusCode(200);
currentContext.set("isSuccess", true);
return null;
}
}
/**
* 封装失败的响应信息,不向下路由
* @param currentContext
*/
private void shouldNotFilter(RequestContext currentContext) {
// 传递false 不向下路由
currentContext.setSendZuulResponse(false);
//设置返回码
currentContext.setResponseStatusCode(401);
//设置返回体
currentContext.setResponseBody("权限不够");
currentContext.set("isSuccess",false);
}
}
三:如何禁用zuul的过滤器
- 禁用系统的过滤器
# 禁用系统的过滤器
zuul:
# 禁用系统的过滤器 系统过滤器的名字: SendResponseFilter
SendResponseFilter:
post:
disable: true
- 禁用自己定义的过滤器
# 禁用自己的过滤器
zuul:
# 自己过滤器的名称
MyPreFilter:
pre:
disable: true
zuul的过滤器的常用功能
一:通过zuul上传大文件
- 这里我们只贴出zuul的配置,文件上传的代码这里不再详细介绍
- 方法1:
(1)设置其超时时间
(2)设置其文件上传的大小 限制,网关设置的文件大小默认就是10M
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
# 设置文件的最大长度
# 或者不配置这个文件,直接加上/zuul 会绕过servlet的 走网关的过滤器
# 1. 配置下面的配置 可以上传大文件
# 2. 在请求后加/zuul 比如:http://localhost:9001/zuul/zuul-fileupload/file
# 记得还需要设置其ribbon和hystrix的超时时间
servlet:
multipart:
max-file-size: 4000MB
max-request-size: 4000MB
# 设置hystrix的超时时间
# 默认计算公式:
# (ribbon.ConnectTimeout + ribbon.ReadTimeout) * (ribbon.MaxAutoRetries + 1) * (ribbon.MaxAutoRetriesNextServer + 1),
# 当配置了后 hystrix 的超时以这个配置的为准,而不会使用默认的公式进行计算
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 600000
# ribbon的超时时间
ribbon:
ConnectTimeout: 100000
ReadTimeout: 100000
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
- 方法2:
(1)访问的时候在请求前加上/zuul,比如:
什么都不用配置,单纯的加上/zuul即可
(2)这样写的目的是写上/zuul会绕过Spring的DispatcherServlet
二:zuul的回退
- 我们的回退分为两个部分,一部分是宕机回退,一部分是超时回退
- 注意我们的回退不能回退4XX和5XX的错误,因为我们Route类型的过滤器RibbonRouteFilter中的方法,处理下游服务器异常的时候,即使下游服务器 报错,5XX或者4XX,但是其返回给zuul的时候结果都是正确的,也就是zuul的try-catch并没有捕捉到,但是其中的返回结果的响应码确实是失败的。
代码编写
(1)yml文件
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 这里设置超时时间是为了设置其超时回退 这里设置为1ms,肯定会过时
# ZUUL的回退机制,不会针对4XX或者5XX的问题
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1
- 这里配置的超时,是为了试验超时回退
- 宕机回退则随便关闭一个真实服务,就可以实现
(2)自定义过滤器实现回退,实现FallbackProvider
package com.xiyou.zull.filter;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 回退的过滤器
* @author 92823
*/
@Component
public class ZullFallBackFilter implements FallbackProvider {
/**
* 表示为什么服务提供回退
* “*” 号表示所有微服务提供回退功能
* 具体的微服务实例名称,指定为哪个微服务回退
* @return
*/
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
/**
* 返回状态码
* @return
* @throws IOException
*/
@Override
public HttpStatus getStatusCode() throws IOException {
// 因为请求网关是成功的,所以是OK
return HttpStatus.OK;
}
/**
* 也是返回状态码
* @return
* @throws IOException
*/
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.OK.value();
}
/**
* 返回Http的响应信息
* @return
* @throws IOException
*/
@Override
public String getStatusText() throws IOException {
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
/**
* 得到响应体
* @return
* @throws IOException
*/
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("后端服务不可用".getBytes());
}
/**
* 响应头的相关信息
* @return
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
// 设置编码格式
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}
(3)主启动类
package com.xiyou.zull;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author 92823
* 回退组件
*/
@EnableDiscoveryClient
@EnableZuulProxy
@SpringBootApplication
public class MyZullFileApplication {
public static void main(String[] args) {
SpringApplication.run(MyZullFileApplication.class, args);
}
}
三:zuul的全局异常处理
- zuul的全局异常处理,其实这里的异常我们处理的也是pre,post等类型,自己写的过滤器中的异常,还是不能处理真实服务传递给我们的4XX和5XX的异常,原因上面说了,我们即使报错,在真实服务返回的时候,也是没有异常的,只是其状态码是4XX和5XX,所以不会被try-catch住
- 我们定制化自己的异常处理的时候,除了需要自己写一个异常的过滤器,一定要关闭系统的SendErrorFilter过滤器
- 系统的SendErrorFilter过滤器其实就是给部分属性赋值,然后直接转发到SpringBoot默认的异常处理器中,SpringBoot的异常处理器,是BasicErrorController
代码实现
(1)yml配置文件
server:
port: 9001
# 注册到eureka服务端的服务名称
spring:
application:
name: my-zuul
eureka:
client:
# 指明eureka服务端的地址
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
instance-id: my-zuul-9001
# 点击具体的微服务,右下角显示微服务的IP
prefer-ip-address: true
# 关闭系统的Error过滤器,自己定制
zuul:
# 禁用系统的过滤器 系统过滤器的名字: SendResponseFilter
SendErrorFilter:
# 过滤器类型
error:
disable: true
- 注意:记得关闭系统的SendErrorFilter过滤器
(2)自己写一个pre类型的过滤器,在里面抛出一个错误
package com.xiyou.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
@Component
public class PreZuulFilter extends ZuulFilter {
@Override
public boolean shouldFilter() {
return true;
}
/**
* 重写其具体的错误处理机制的逻辑
* @return
*/
@Override
public Object run() {
throw new RuntimeException("我有错误了");
}
@Override
public String filterType() {
return "post";
}
@Override
public int filterOrder() {
return 0;
}
}
(3)自定义的异常过滤器
package com.xiyou.zuul.filter;
import com.alibaba.fastjson.JSONObject;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* @author 92823
* 利用系统的SendErrorFilter过滤器处理自己的逻辑
*/
@Component
public class ZuulErrorFilter1 extends SendErrorFilter {
/**
* 重写其具体的错误处理机制的逻辑
* @return
*/
@Override
public Object run() {
try {
// 得到当前的容器
RequestContext requestContext = RequestContext.getCurrentContext();
// 找寻Zuul的异常
ExceptionHolder zuulException = findZuulException(requestContext.getThrowable());
// 得到当前的request
HttpServletRequest request = requestContext.getRequest();
// 得到当前的响应
HttpServletResponse response = requestContext.getResponse();
// 设置其响应的状态码和返回结果
// 设置其编码格式,避免乱码
response.setContentType("appliation/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
Map<String,Object> errorMap = new HashMap<>();
errorMap.put("code","-1");
errorMap.put("errMsg",zuulException.getThrowable().getCause().getMessage());
response.getWriter().print(JSONObject.toJSONString(errorMap));
} catch (Exception e) {
ReflectionUtils.rethrowRuntimeException(e);
}
return null;
}
}
四:zuul的跨域请求
- 跨域一般是针对前端的请求来说的
- 当我们的ip不同或者端口号不同的时候,就会造成跨域的问题
代码编写
- 我们只需要在zuul里面写一个配置类即可,代码如下
package com.xiyou.zuul.config;
import org.springframework.web.filter.CorsFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* 解决跨域 跨域就是不同的IP或者不同的端口
* @author 92823
*/
@Configuration
public class ZuulConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// 允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
zuul的高可用
- 将zuul注册到Eureka中 :
- 我们将zuul的客户端和服务端都注册进Eureka中,这样就可以实现Zuul的高可用。但是不推荐
- 利用Nginx等插件实现高可用
- 我们通过在nginx中配置上游服务器,配置多个zuul服务的地址来实现zuul的高可用