Zuul网关是将一个业务系统内部的多个微服务进行封装,对外提供唯一访问入口,实现系统内高内聚,系统间通过网关交互达到松耦合的效果。它可以和Eureka、Ribbon、Hystrix等组件配合使用,实现身份认证与安全、审查与监控、动态路由、压力测试、负载均衡、流量控制等功能
为什么需要Zuul
Zuul作为路由网关组件,在微服务架构中有着非常重要的作用,主要体现在下面6各方面:
- Zuul、Ribbon、以及Eureka相结合,可以实现智能路由和负载均衡的功能,Zuul能够将请求流量按某种策略分发到集群状态的多个实例。
- 网关将所有服务的API接口统一聚合,并统一对外暴露。外界系统调用API接口时都是由网关对外暴露的API接口,外界系统不需要知道微服务系统中各系统相互调用的复杂性。微服务系统夜保护了其内部微服务单元的API接口,防止被外界直接调用,导致服务的敏感信息对外暴露。
- 网关服务可以做用户身份认证和权限认证,防止非法请求操作API接口,对服务起到保护作用。
- 网关可以实现监控功能,实时日志输出,对日志进行记录。
- 网关可以用来实现流量监控,在高流量的情况下对服务进行降级。
- API接口从内部服务分离出来,方便做测试。
Zuul的工作原理
Zuul是通过Servlet来实现的,Zuul通过自定义的Servlet(类似于Spring MVC的DispatcServlet)来对请求进行控制。Zuul的核心是一系列的过滤器,可以在Http请求的发起和响应返回期间执行一些列的过滤。
- PRE::过滤规则在路由之前起作用。可以利用”Pre“过滤器实现用户鉴权,记录请求日志等;
- ROUTING:过滤规则在路由时发生作用。可以利用”Routing“过滤器实现动态路由、灰度发布、A/B测试、负载限流等。
- POST:过滤规则在路由之后发生作用。可以利用”Post“过滤器手机统计信息和指标,将微服务的想要写入HTTP相应并返回给服务消费者。
- ERROR:过滤规则路由过程中发生错误时发生作用。可以利用Error过滤器记录错误日志,并对错误进行二次处理等。
Zuul采取动态读取、编译和运行这些过滤器。过滤器之间不能直接相互通信,而是通过RequestContext对象来共享数据,每个请求都会创建一个RequestContext对象。Zuul过滤器具有下面这些关键特性:
- Type:Zuul过滤器的类型,这个类型决定了过滤器在请求的哪个阶段起作用,例如Pre、Post阶段等。
- Execution Order:执行顺序,规定了过滤器的执行顺序,Order值越小越先执行。
- Criteria:标准,Filter执行所需的条件。
- Action:如果符合执行条件则执行Action(即逻辑代码)。
Zuul请求的生命周期如下图所示:
当一个客户端Request请求进入Zuul网关服务时,网关进入“pre filter”,进行一系列的验证、操作或者判断。然后交给"routing filter"进行路由转发,转发到具体的服务实例进行逻辑处理、返回数据。当具体的服务处理完后,最后由"post filter"进行处理,该类型的处理器处理完后,将Response信息返回给客户端。
ZuulServlet时Zuul的核心Servlet。ZuulServlet的作用是初始化ZuulFilter,并编排这些ZuulFilter的执行顺序。该类中有一个service()方法,执行了过滤器的执行逻辑。
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
try {
this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
this.preRoute();
} catch (ZuulException var13) {
this.error(var13);
this.postRoute();
return;
}
try {
this.route();
} catch (ZuulException var12) {
this.error(var12);
this.postRoute();
return;
}
try {
this.postRoute();
} catch (ZuulException var11) {
this.error(var11);
}
} catch (Throwable var14) {
this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
从上面的代码中可以知道,首先执行了preRoute()方法,这个方法执行了是PRE类型的过滤器逻辑。如果执行这个方法时出错了,那么会执行error(e)和postRoute()方法。然后执行route()方法,该方法是执行ROUTING类型过滤器的逻辑。最后执行postRoute(),该方法执行了POST类型过滤器的逻辑。