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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取 2000+ 道 Java 面试题

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

其架构如下图所示:

20191123100234\_1.png
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。

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

20191123100234\_2.png

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


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring Cloud源码解析之如何集成Zuul

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏