Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。Zuul 可以适当的对多个 Amazon Auto Scaling Groups 进行路由请求。
其架构如下图所示:
Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。过滤器之间没有直接的相互通信。他们是通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。
过滤器是由Groovy写成。这些过滤器文件被放在Zuul Server上的特定目录下面。Zuul会定期轮询这些目录。修改过的过滤器会动态的加载到Zuul Server中以便于request使用。
下面有几种标准的过滤器类型:
- PRE:这种过滤器在请求到达Origin Server之前调用。比如身份验证,在集群中选择请求的Origin Server,记log等。
- ROUTING:在这种过滤器中把用户请求发送给Origin Server。发送给Origin Server的用户请求在这类过滤器中build。并使用Apache HttpClient或者Netfilx Ribbon发送给Origin Server。
- POST:这种过滤器在用户请求从Origin Server返回以后执行。比如在返回的response上面加response header,做各种统计等。并在该过滤器中把response返回给客户。
- ERROR:在其他阶段发生错误时执行该过滤器。
- 客户定制:比如我们可以定制一种STATIC类型的过滤器,用来模拟生成返回给客户的response。
过滤器的生命周期如下所示:
Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
- 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
- 审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
- 动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
- 压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
- 负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
- 静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
- 多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
除此之外,Netflix公司还利用Zuul的功能通过金丝雀版本实现精确路由与压力测试。
其核心代码为(ZuulServlet):
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
try {
preRouting();
} catch (ZuulException e) {
error(e);
postRouting();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
try {
routing();
} catch (ZuulException e) {
error(e);
postRouting();
return;
}
try {
postRouting();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_FROM_FILTER_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
Spring Cloud NetFlix集成了Zuul,可以直接在Application上使用@EnableZuulProxy,从而可以直接启动Zuul,这是怎么实现的呢?
首先,我们先看一下@EnableZuulProxy
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
这个类里import了类ZuulProxyMarkerConfiguration。然后再看一下这个类里有什么?
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
就一个类定义,什么也没有啊?这条路断了。。
这条路不通,我们在从另外一个入口进去,看下上面两个类所在的jar包里的文件:src/main/resources/META-INF/spring.factories。看一下这个文件里的内容,有一句是这样的:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration
Application启动的时候会自动加载ZuulProxyAutoConfiguration这个类。我们看一下这类的定义:
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
......
}
它的头部有@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)。这表示,如果ZuulProxyMarkerConfiguration.Marker.class这个类被加载了,那么ZuulProxyAutoConfiguration就也会被加载。
ok,找到真正的入口了。。。。。。
这个类里有什么呢?加载了一些Filter和路由的Bean。包括DiscoveryClient和Ribbon相关的,以便于在Spring Cloud里进行服务的直接调用和路由。
下一步,ZuulServlet是在哪里加载起来的呢?
我们看一下这个包:org.springframework.cloud.netflix.zuul.web里,里面有个类:
public class ZuulController extends ServletWrappingController {
public ZuulController() {
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// We don't care about the other features of the base class, just want to
// handle the request
return super.handleRequestInternal(request, response);
}
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilter
RequestContext.getCurrentContext().unset();
}
}
}
嗯,看到了。。ZuulServlet是在这里定义的。。然后再看ZuulProxyAutoConfiguration 的基类 ZuulServerAutoConfiguration
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
en, 加载起来了。。
我们在application-xxx.properties里面配置的routes在哪里加载呢?
@Autowired
protected ZuulProperties zuulProperties;
@Bean
@Primary
public CompositeRouteLocator primaryRouteLocator(
Collection<RouteLocator> routeLocators) {
return new CompositeRouteLocator(routeLocators);
}
@Bean
@ConditionalOnMissingBean(SimpleRouteLocator.class)
public SimpleRouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(this.server.getServletPrefix(),
this.zuulProperties);
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
return mapping;
}
这里。。
那Filter是在哪里被加载的呢?
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
看这个类里,filters这个变量是@Autowired的,所有继承ZuulFilter的类都会被组装到filters这个Map里。
然后再看ZuulFilterInitializer类里:
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
所有的Filter都被注册到了Zuul的filterRegistry里。。这样所有的Filter就都注册好了。。
那过来的Request是怎么路由出去的呢?
看PreDecorationFilter类里:把HrrpServletRequest里的url path拿出来,换成了Route对象,这个对象里指定了这个Path路由到哪个服务或地址。。