一、什么是zuul?
Zuul 是 Netflix OSS 中的一员,是一个基于 JVM 路由和服务端的负载均衡器。提供路由、监控、弹性、安全等方面的服务框架。Zuul 能够与 Eureka、Ribbon、Hystrix 等组件配合使用。
Zuul 的核心是过滤器,通过这些过滤器我们可以扩展出很多功能,比如:
1)动态路由
动态地将客户端的请求路由到后端不同的服务,做一些逻辑处理,比如聚合多个服务的数据返回。
2)请求监控
可以对整个系统的请求进行监控,记录详细的请求响应日志,可以实时统计出当前系统的访问量以及监控状态。
3)认证鉴权
对每一个访问的请求做认证,拒绝非法请求,保护好后端的服务。
4)压力测试
压力测试是一项很重要的工作,像一些电商公司需要模拟更多真实的用户并发量来保证重大活动时系统的稳定。通过 Zuul 可以动态地将请求转发到后端服务的集群中,还可以识别测试流量和真实流量,从而做一些特殊处理。
5)灰度发布
灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
Zuul网关过滤器
Zuul中提供了过滤器定义,可以用来过滤代理请求,提供额外功能逻辑。如:权限验证,日志记录等。
Zuul提供的过滤器是一个父类。父类是ZuulFilter。通过父类中定义的抽象方法filterType,来决定当前的Filter种类是什么。有前置过滤、路由后过滤、后置过滤、异常过滤。
- 前置过滤:是请求进入Zuul之后,立刻执行的过滤逻辑。
- 路由后过滤:是请求进入Zuul之后,并Zuul实现了请求路由后执行的过滤逻辑,路由后过滤,是在远程服务调用之前过滤的逻辑。
- 后置过滤:远程服务调用结束后执行的过滤逻辑。
- 异常过滤:是任意一个过滤器发生异常或远程服务调用无结果反馈的时候执行的过滤逻辑。无结果反馈,就是远程服务调用超时。
3.1 过滤器实现方式
继承父类ZuulFilter。在父类中提供了4个抽象方法,分别是:filterType, filterOrder, shouldFilter, run。其功能分别是:
- filterType:方法返回字符串数据,代表当前过滤器的类型。可选值有-pre, route, post, error。
pre - 前置过滤器,在请求被路由前执行,通常用于处理身份认证,日志记录等;
route - 在路由执行后,服务调用前被调用;
error - 任意一个filter发生异常的时候执行或远程服务调用没有反馈的时候执行(超时),通常用于处理异常;
post - 在route或error执行后被调用,一般用于收集服务信息,统计服务性能指标等,也可以对response结果做特殊处理。
- filterOrder:返回int数据,用于为同filterType的多个过滤器定制执行顺序,返回值越小,执行顺序越优先。
- shouldFilter:返回boolean数据,代表当前filter是否生效。
- run:具体的过滤执行逻辑。如pre类型的过滤器,可以通过对请求的验证来决定是否将请求路由到服务上;如post类型的过滤器,可以对服务响应结果做加工处理(如为每个响应增加footer数据)。
二、zuul的工作原理
1、过滤器机制
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。
Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。
Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。
Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用。
下面有几种标准的过滤器类型:
Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。
(1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
(2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
(3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
(4) ERROR:在其他阶段发生错误时执行该过滤器。
内置的特殊过滤器
zuul还提供了一类特殊的过滤器,分别为:StaticResponseFilter和SurgicalDebugFilter
StaticResponseFilter:StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源。
SurgicalDebugFilter:SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机。
自定义的过滤器
除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。
例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
2、过滤器的生命周期
Zuul请求的生命周期如图,该图详细描述了各种类型的过滤器的执行顺序。
Zuul网关的容错(与Hystrix的无缝结合)
在spring cloud中,Zuul启动器中包含了Hystrix相关依赖,在Zuul网关工程中,默认是提供了Hystrix Dashboard服务监控数据的(hystrix.stream),但是不会提供监控面板的界面展示。可以说,在spring cloud中,zuul和Hystrix是无缝结合的。
4.1 Zuul中的服务降级处理
在Edgware版本之前,Zuul提供了接口ZuulFallbackProvider用于实现fallback处理。从Edgware版本开始,Zuul提供了ZuulFallbackProvider的子接口FallbackProvider来提供fallback处理。
Zuul的fallback容错处理逻辑,只针对timeout异常处理,当请求被Zuul路由后,只要服务有返回(包括异常),都不会触发Zuul的fallback容错逻辑。
因为对于Zuul网关来说,做请求路由分发的时候,结果由远程服务运算的。那么远程服务反馈了异常信息,Zuul网关不会处理异常,因为无法确定这个错误是否是应用真实想要反馈给客户端的。
代码:
/** * 如果需要在Zuul网关服务中增加容错处理fallback,需要实现接口ZuulFallbackProvider * spring-cloud框架,在Edgware版本(包括)之后,声明接口ZuulFallbackProvider过期失效, * 提供了新的ZuulFallbackProvider的子接口 - FallbackProvider * 在老版本中提供的ZuulFallbackProvider中,定义了两个方法。 * - String getRoute() * 当前的fallback容错处理逻辑处理的是哪一个服务。可以使用通配符‘*’代表为全部的服务提供容错处理。 * 如果只为某一个服务提供容错,返回对应服务的spring.application.name值。 * - ClientHttpResponse fallbackResponse() * 当服务发生错误的时候,如何容错。 * 新版本中提供的FallbackProvider提供了新的方法。 * - ClientHttpResponse fallbackResponse(Throwable cause) * 如果使用新版本中定义的接口来做容错处理,容错处理逻辑,只运行子接口中定义的新方法。也就是有参方法。 * 是为远程服务发生异常的时候,通过异常的类型来运行不同的容错逻辑。 */ @Component public class TestFallBbackProvider implements FallbackProvider { /** * return - 返回fallback处理哪一个服务。返回的是服务的名称 * 推荐 - 为指定的服务定义特性化的fallback逻辑。 * 推荐 - 提供一个处理所有服务的fallback逻辑。 * 好处 - 服务某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑,有一个通用的。 */ @Override public String getRoute() { return "eureka-application-service"; } /** * fallback逻辑。在早期版本中使用。 * Edgware版本之后,ZuulFallbackProvider接口过期,提供了新的子接口FallbackProvider * 子接口中提供了方法ClientHttpResponse fallbackResponse(Throwable cause)。 * 优先调用子接口新定义的fallback处理逻辑。 */ @Override public ClientHttpResponse fallbackResponse() { System.out.println("ClientHttpResponse fallbackResponse()"); List<Map<String, Object>> result = new ArrayList<>(); Map<String, Object> data = new HashMap<>(); data.put("message", "服务正忙,请稍后重试"); result.add(data); ObjectMapper mapper = new ObjectMapper(); String msg = ""; try { msg = mapper.writeValueAsString(result); } catch (JsonProcessingException e) { msg = ""; } return this.executeFallback(HttpStatus.OK, msg, "application", "json", "utf-8"); } /** * fallback逻辑。优先调用。可以根据异常类型动态决定处理方式。 */ @Override public ClientHttpResponse fallbackResponse(Throwable cause) { System.out.println("ClientHttpResponse fallbackResponse(Throwable cause)"); if(cause instanceof NullPointerException){ List<Map<String, Object>> result = new ArrayList<>(); Map<String, Object> data = new HashMap<>(); data.put("message", "网关超时,请稍后重试"); result.add(data); ObjectMapper mapper = new ObjectMapper(); String msg = ""; try { msg = mapper.writeValueAsString(result); } catch (JsonProcessingException e) { msg = ""; } return this.executeFallback(HttpStatus.GATEWAY_TIMEOUT, msg, "application", "json", "utf-8"); }else{ return this.fallbackResponse(); } } /** * 具体处理过程。 * @param status 容错处理后的返回状态,如200正常GET请求结果,201正常POST请求结果,404资源找不到错误等。 * 使用spring提供的枚举类型对象实现。HttpStatus * @param contentMsg 自定义的响应内容。就是反馈给客户端的数据。 * @param mediaType 响应类型,是响应的主类型, 如: application、text、media。 * @param subMediaType 响应类型,是响应的子类型, 如: json、stream、html、plain、jpeg、png等。 * @param charsetName 响应结果的字符集。这里只传递字符集名称,如: utf-8、gbk、big5等。 * @return ClientHttpResponse 就是响应的具体内容。 * 相当于一个HttpServletResponse。 */ private final ClientHttpResponse executeFallback(final HttpStatus status, String contentMsg, String mediaType, String subMediaType, String charsetName) { return new ClientHttpResponse() { /** * 设置响应的头信息 */ @Override public HttpHeaders getHeaders() { HttpHeaders header = new HttpHeaders(); MediaType mt = new MediaType(mediaType, subMediaType, Charset.forName(charsetName)); header.setContentType(mt); return header; } /** * 设置响应体 * zuul会将本方法返回的输入流数据读取,并通过HttpServletResponse的输出流输出到客户端。 */ @Override public InputStream getBody() throws IOException { String content = contentMsg; return new ByteArrayInputStream(content.getBytes()); } /** * ClientHttpResponse的fallback的状态码 返回String */ @Override public String getStatusText() throws IOException { return this.getStatusCode().getReasonPhrase(); } /** * ClientHttpResponse的fallback的状态码 返回HttpStatus */ @Override public HttpStatus getStatusCode() throws IOException { return status; } /** * ClientHttpResponse的fallback的状态码 返回int */ @Override public int getRawStatusCode() throws IOException { return this.getStatusCode().value(); } /** * 回收资源方法 * 用于回收当前fallback逻辑开启的资源对象的。 * 不要关闭getBody方法返回的那个输入流对象。 */ @Override public void close() { } }; } }
Zuul网关的限流保护
Zuul网关组件也提供了限流保护。当请求并发达到阀值,自动触发限流保护,返回错误结果。只要提供error错误处理机制即可。
Zuul的限流保护需要额外依赖spring-cloud-zuul-ratelimit组件。
Zuul网关性能调优:网关的两层超时调优
使用Zuul的spring cloud微服务结构图:
从上图中可以看出。整体请求逻辑还是比较复杂的,在没有zuul网关的情况下,app client请求app service的时候,也有请求超时的可能。那么当增加了zuul网关的时候,请求超时的可能就更明显了。
当请求通过zuul网关路由到服务,并等待服务返回响应,这个过程中zuul也有超时控制。zuul的底层使用的是Hystrix+ribbon来实现请求路由。结构如下:
zuul中的Hystrix内部使用线程池隔离机制提供请求路由实现,其默认的超时时长为1000毫秒。ribbon底层默认超时时长为5000毫秒。如果Hystrix超时,直接返回超时异常。如果ribbon超时,同时Hystrix未超时,ribbon会自动进行服务集群轮询重试,直到Hystrix超时为止。如果Hystrix超时时长小于ribbon超时时长,ribbon不会进行服务集群轮询重试。
转自: https://www.cnblogs.com/jing99/p/11696192.html