Zuul 由Netflix公司开发,是基于JVM的路由器和服务端负载均衡器。事实上,网关承载着路由、负载均衡、安全认证、流量控制、灰度发布、熔断等很多重要功能。
本文将简要学习Zuul的理论和源码,看看其核心功能 Router 和 Filter。
从看电影说起
先以看电影场景来类比下网关。上映2部3D电影,A电影在1、2号厅放映,B电影在3号厅放映。
- S1.观众进入电影院大厅,凭验证码或二维码取票,到时间点后检票入场(没票则拒绝入场),拿3D眼镜
[pre]取票(eg:普通token转JWT token)、检票(安全认证)、发3D眼镜(预处理请求之类)
- S2.观众根据电影票上的厅号进入1、2、3厅
[route]看A或B电影(路由)、看A电影的进12厅(负载均衡)
- S3.看完电影后退回3D眼镜
[postRoute]处理Response
当然,观众就是流量(Request)。生活中例子其实很多,如:景区检票、小区大门物业、公司大门安保、交通安检、各类活动入场等等。
网关,只是在计算机世界而已,并无特别之处,就是一个网络关卡。
Zuul核心功能
Router and Filter: Zuul 是Spring Cloud手册上的标题,Zuul两个核心功能就是:路由 与 过滤。我们可以基于这两个核心功能做大量的事情。
路由 是根据规则做流量转发,犹如婚礼落座、交警指挥交通等。路由用来转发数据流量,无特别之处。
过滤 是在请求处理前后做些事情,想玩转请求都可通过Filter来实现。你就是剪刀手,想把请求剪成什么模样全看你心情。
源码学习
本文源码基于spring-cloud-netflix-core:1.3.0.RELEASE,下面所有示例代码中仅为必要代码,并非全部源码,文章以适合阅读为宜。
现在Servlet虽然退居幕后,但指挥棒还是在Servlet这位指挥官手中,下面看看源码。
魔法入口
用注解 @EnableZuulProxy
标记Spring Boot应用,便获得Zuul的所有能力。
@Configuration
public class ZuulProxyConfiguration extends ZuulConfiguration {}
注解Import了 ZuulProxyConfiguration
,配置中注入RibbonRoutingFilter 和 SimpleHostRoutingFilter两个用于路由的过滤器。
@Configuration
public class ZuulProxyConfiguration extends ZuulConfiguration {
@Bean
public RibbonRoutingFilter ribbonRoutingFilter(...,
RibbonCommandFactory<?> ribbonCommandFactory) {
}
@Bean
public SimpleHostRoutingFilter simpleHostRoutingFilter(...) {
return new SimpleHostRoutingFilter(helper, zuulProperties);
}
}
请求通过网关到达具体服务的整个过程,都是通过Filter来搞定。pre 类型Filter负责事前处理,route 类型Filter负责路由,postRoute 类型Filter负责善后,error 类型负责擦屁股(处理异常)。
其他Filter各司其职,这里不做介绍。
指挥官战略
ZuulProxyConfiguration继承ZuulConfiguration,一个是标准Zuul配置,一个是具备proxy功能的配置。
ZuulConfiguration核心功能之一就是创建zuulServlet,zuulServlet才是请求的实际处理者。而Zuul的所有强大功能,都得益于这位Boss的顶层设计。
@Configuration
public class ZuulConfiguration {
@Bean
@ConditionalOnMissingBean(name = "zuulServlet")
public ServletRegistrationBean zuulServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
this.zuulProperties.getServletPattern());
return servlet;
}
}
现在开始复习大学课程,学习Servlet的 init()、service() 方法。熟知的DispatcherServlet也不过只是HttpServlet的 “重孙类” 而已。
public class ZuulServlet extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
zuulRunner = new ZuulRunner(bufferReqs);
}
@Override
public void service(servletRequest, servletResponse){
try {
init(...);
// 执行所有pre类型Filter
try { preRoute();} catch (ZuulException e) {
error(e); postRoute(); return;}
// 执行所有route类型Filter
try { route(); } catch (ZuulException e) {
error(e); postRoute(); return; }
// 执行所有postRoute类型Filter
try { postRoute(); } catch (ZuulException e) {
error(e); return;
}
}
}
}
整个Zuul的设计如此简单。按 路由前、路由中、路由后 顺序执行各类Filter,出错就catch住并进行error处理。
指挥官实操
在 preRoute()、route()、postRoute()、error(e)四个函数中,做的事情都是一样的,就是运行对应的Filter。以route()为例:
void route() throws ZuulException {
zuulRunner.route();
}
用 ZuulRunner 这位跑腿大叔来代为执行,跑腿大叔果然只负责跑腿,转身就丢给路由专家 FilterProcessor。
public class FilterProcessor {
public void route() throws ZuulException {
runFilters("route");
}
}
FilterProcessor不愧是专家,提供了个 runFilters() 这个通用函数,不管什么类型,来者不拒。runFilters(“route”)、runFilters(“error”)、runFilters(“post”)、runFilters(“pre”) 各种过滤器通通可以帮忙执行。
public Object runFilters(String sType) throws Throwable {
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
}
}
}
runFilters() 就是从 FilterRegistry 中取出对应类型的Filters,以for循环逐一执行各Filter。
另,现在XXRegistry、XXHub特别多,像Docker Registry、Dockerhub、Github等等,各种同类交友集中地。FilterRegistry也就是各位Filter的宿舍,各Filter创建之后就进入了Registry。
路由怎么实现
一路上修修补补请求倒不是什么难事,可路由毕竟是个体力活,要把请求从网关转发到背后真正的服务。
像Nginx、Apache、Haproxy等都有着数据转发的功能,那Zuul又如何实现?
入口处特意提到 SimpleHostRoutingFilter 和 RibbonRoutingFilter,这两位就是在两种场景下负责路由的哥们。先看看SimpleHostRoutingFilter。
public class SimpleHostRoutingFilter extends ZuulFilter {
private CloseableHttpClient httpClient;
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null && ...;
}
@Override
public Object run() {
CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
}
}
CloseableHttpClient 是apache httpclient 中的类。SimpleHostRoutingFilter中两个重要方法,一是shouldFilter()即什么情况下会进行路由;而是run()即路由处理逻辑。
继续跟一下run()中forward的实际处理,其实就是 用HttpClient发起了个HTTP请求。
httpclient.execute(httpHost, httpRequest);
至于是走SimpleHostRoutingFilter还是RibbonRoutingFilter,下面简单说明下。
如果是下面这种配置,RouteHost有值,走的就是SimpleHostRoutingFilter。
zuul.routes.user-service.url=http://localhost:8080
zuul.routes.user-service.path=/api/users/**
如果是下面的配置,配的serviceId,走的是RibbonRoutingFilter。
zuul.routes.user-service.path=/api/users/**
zuul.routes.user-service.serviceId: user-service
本篇暂不深入RibbonRoutingFilter,它涉及到Ribbon及Hystrix。