一、概述
Spring Cloud Zuul 是 Spring Cloud Netflix 子项目的核心组件之一,是netflix开源的一个API Gateway服务器,本质上有一个Web Servlet应用,可以作为微服务架构中的 API 网关使用,支持动态路由与过滤功能;网关为微服务提供统一的访问入口;网关的定义类似设计模式中的门面模式,相当于微服务中的门面,客户端访问微服务都是通过它进行路由及过滤。它提供请求路由,负载均衡、校验过滤、服务容错、服务聚合等功能。
二、 Zuul功能和作用
Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
▪验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
▪审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
▪路由转发: 以动态方式根据需要将请求路由至不同后端集群处。
▪压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
▪负载均衡: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
▪静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
▪多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
除以上功能之外,Netflix公司还利用Zuul的功能通过金丝雀版本实现精确路由与压力测试。
在Spring Cloud中,可用通过添加Zuul的依赖和配置文件,快速搭建一个网关,方便统一管理和维护各个微服务,实现更好的服务治理。
三、Zuul网关工作原理
3.1 网关主要组件:
Zuul网关主要由一下几个组件构成:
▪Filter:过滤器,可以在请求被路由前或者之后添加一些处理逻辑。
▪Route:路由,将请求路由到不同的后端服务上。
▪Ribbon:负载均衡器,Zuul 默认使用 Ribbon 进行负载均衡。
▪Hystrix:容错处理器,可以实现限流和熔断机制。
Zuul 的过滤器链是整个网关的核心部分,它由多个过滤器构成,每个过滤器都负责不同的处理逻辑,比如请求的鉴权、转发等操作。过滤器链在处理请求的过程中,会依次执行这些过滤器,从而实现对请求的全生命周期管理,具体流程如下图所示。
在图中,我们可以看到 Filter Chain 主要由三部分组成:过滤器、生成路由并发送给后端服务、处理路由响应。下面我们将详细介绍每个部分的处理逻辑。
3.2 Zuul过滤器
过滤器是 Zuul 中最重要的组件之一,它可以拦截和修改请求和响应的内容,实现各种功能。在 Spring Cloud 中,所有的过滤器都必须继承抽象类 ZuulFilter,并实现其中的四个方法:
▪filterType()方法:返回过滤器类型,包括 pre、post、route 和 error 四种类型。
▪filterOrder()方法:返回过滤器执行的顺序,值越小越先执行。
▪shouldFilter()方法:判断过滤器是否需要执行,默认返回 true,表示全部需要执行。
▪run()方法:过滤器的主要业务逻辑,实现具体的过滤逻辑
Filter 类型
Zuul 中不同类型 filter 的执行逻辑核心在 com.netflix.zuul.http.ZuulServlet 类中定义,该类相关代码如下:
@Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try { init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); // Marks this request as having passed through the "Zuul engine", as opposed to servlets // explicitly bound in web.xml, for which requests will not have the same data attached RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; } } catch (Throwable e) { error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
这张经典的官方流程图有些问题,其中 post Filter 抛错之后进入 error Filter,然后再进入 post Filter 是有失偏颇的。实际上 post Filter 抛错分两种情况:
1.在 post Filter 抛错之前,pre、route Filter 没有抛错,此时会进入 ZuulException 的逻辑,打印堆栈信息,然后再返回 status = 500 的 ERROR 信息。
2.在 post Filter 抛错之前,pre、route Filter 已有抛错,此时不会打印堆栈信息,直接返回status = 500 的 ERROR 信息。
这样就比较直观地描述了 Zuul 关于 Filter 的请求生命周期。Zuul 中一共有四种不同生命周期的 Filter,分别是:
1.pre:在 Zuul 按照规则路由到下级服务之前执行。如果需要对请求进行预处理,比如鉴权、限流等,都应考虑在此类 Filter 实现。
2.route:这类 Filter 是 Zuul 路由动作的执行者,是 Apache Http Client 或 Netflix Ribbon 构建和发送原始 HTTP 请求的地方,目前已支持 Okhttp。
3.post:这类 Filter 是在源服务返回结果或者异常信息发生后执行的,如果需要对返回信息做一些处理,则在此类 Filter 进行处理。
4.error:在整个生命周期内如果发生异常,则会进入 error Filter,可做全局异常处理。
实际项目中,需要自实现以上类型的 Filter 来对请求链路进行处理,根据业务的需求,选取相应生命周期的 Filter 来达成目的。在 Filter 之间,通过 com.netflix.zuul.context.RequestContext 类来进行通信,内部采用 ThreadLocal 保存每个请求的一些信息,包括请求路由、错误信息、HttpServletRequest、HttpServletResponse,这使得一些操作是十分可靠的,它还扩展了 ConcurrentHashMap,目的是为了在处理过程中保存任何形式的信息。
3.3 生成路由并发送给后端服务
在Zuul中,可以通过配置ZuulProperties和RouteLocator实现路由转发的功能。其中ZuulProperties主要用于配置Zuul服务的一些相关属性,比如缓存时间、URL的前缀和后缀等;而RouteLocator则用于定义多个路由规则,将请求映射到不同的后端服务上。
当请求进入Zuul网关之后,首先会经过一系列过滤器处理,然后根据路由规则将请求转发到对应的后端服务上,最终返回响应结果。在转发请求时,Zuul可以自动地根据负载均衡策略选择相应的服务器,实现负载均衡的功能。
3.4 处理路由响应
在Zuul中,如果后端服务响应异常或者错误,那么 Zuul 会将这个异常封装成一个 ZuulException 对象,并交给其它的过滤器进行处理。当所有过滤器执行完毕之后,Zuul 会根据 ZuulException 中的状态码和错误消息,返回相应的响应结果。
四、Zuul 网关配置过程
在 Spring Cloud 中,我们可以通过添加一些依赖和配置文件,快速地创建一个 Zuul 网关服务。下面我们将详细介绍如何进行配置。
4.1 添加pom.xml依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
4.2 创建配置类
创建一个配置类,用于配置 Zuul 网关服务的相关属性。
@Configuration @EnableZuulProxy public class ZuulConfig { // 配置 Zuul 网关服务的相关属性 }
其中注解 @EnableZuulProxy 表示开启 Zuul 的代理功能,可以自动注册到 Eureka 服务中心,并集成 Ribbon 和 Hystrix 等组件。
4.3 配置路由规则
接下来我们需要配置 Zuul 的路由规则,将请求转发到不同的后端服务上。
@Bean public RouteLocator routeLocator(RouteLocatorBuilder builder) { return builder.routes() .route("service-a", r -> r.path("/service-a/**") .filters(f -> f.stripPrefix(1)) .uri("http://localhost:6060")) .route("service-b", r -> r.path("/service-b/**") .filters(f -> f.stripPrefix(1)) .uri("http://localhost:6061")) .build(); }
在上面的代码中,我们定义了两个路由规则,分别将请求转发到 http://localhost:8081 和 http://localhost:8082 这两个地址上。其中 stripPrefix(1) 表示去掉 URL 中第一个斜杠之后的内容。
4.4 配置路由规则
最后我们可以添加一些过滤器,实现不同的功能。
@Bean public MyFilter myFilter() { return new MyFilter(); }
其中 MyFilter 是我们自定义的过滤器类,用于实现一些特定的功能。
四、总结
本文从 Zuul 网关的原理、使用场景和配置过程三个方面详细介绍了 Zuul 网关的相关知识。可以看出,Zuul 的过滤器链是整个网关的核心部分,通过添加不同的过滤器,可以实现不同的功能,比如鉴权、转发、限流等。同时,通过合理地配置路由规则,可以将请求快速地转发到相应的后端服务中,实现负载均衡和服务治理的功能。
在实际开发中,我们可以根据不同的需求,灵活地运用 Zuul 网关服务,构建高可用、高并发的分布式应用系统。