Spring Cloud源码解析之如何集成Zuul

Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。Zuul 可以适当的对多个 Amazon Auto Scaling Groups 进行路由请求。

其架构如下图所示:


《Spring Cloud源码解析之如何集成Zuul》
 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。

过滤器的生命周期如下所示:

《Spring Cloud源码解析之如何集成Zuul》


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路由到哪个服务或地址。。

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/mytobaby00/article/details/79825540
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞