欢迎访问Spring Cloud中国社区

《重新定义Spring Cloud实战》由Spring Cloud中国社区倾力打造,基于Spring Cloud的Finchley.RELEASE版本,本书内容宽度足够广、深度足够深,而且立足于生产实践,直接从生产实践出发,包含大量生产实践的配置。欢迎加微信Software_King进群答疑,国内谁在使用Spring Cloud?欢迎登记

Spring Cloud Zuul遗失的世界(一)

xujin · 5年前 · 3861 ·

摘要: Zuul作为java网关届的鼻祖,2016年自研网关中间件的时候,对其源码看了很多次,经过两大互联网公司中间件的洗礼之后,目前轮到自己设计一个网关中间件纳管Spring Cloud。最近抽空把自己的理解,备注一下。由于Spring cloud整合Zuul的代码过多。本文主要介绍Spring Cloud对Netflix Zuul高度抽象封装整合部分。即spring-cloud-netflix-core的代码。

一.Spring Cloud Zuul源码分析

1.1 @EnableZuulProxy@EnableZuulServer

如下主应用程序代码所示,我们使用Spring Cloud Zuul只需要加上@EnableZuulProxy@EnableZuulServer两种注解就可以。

@SpringBootApplication
@EnableZuulProxy
public class SpringCloudZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringCloudZuulApplication.class, args);
    }
}

@EnableZuulProxy@EnableZuulServer,
@EnableZuulServer - 普通Zuul Server,只支持基本的route与filter功能.
@EnableZuulProxy - 普通Zuul Server+服务发现与熔断等功能的增强版,具有反向代理功能.

1.2 @EnableZuulProxy注解入口

点开注解@EnableZuulProxy,进入到org.springframework.cloud.netflix.zuul.EnableZuulProxy,如下所示。

@EnableCircuitBreaker
@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyConfiguration.class)
public @interface EnableZuulProxy {
}

1.3 ZuulProxyConfiguration代码

@Import(ZuulProxyConfiguration.class),查看导入的类org.springframework.cloud.netflix.zuul.ZuulProxyConfiguration,如下所示,可以看到org.springframework.cloud.netflix.zuul.ZuulProxyConfiguration,继承了上文的ZuulConfiguration,新增了服务与实例等概念,核心重要代码已经加入注释

@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
public class ZuulProxyConfiguration extends ZuulConfiguration {

    @SuppressWarnings("rawtypes")
    @Autowired(required = false)
    private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();

    // DiscoveryClient肩负着从Eureka中获取服务列表,获取对应实例的功能
    @Autowired
    private DiscoveryClient discovery;

    @Autowired
    private ServiceRouteMapper serviceRouteMapper;

    // zuulFeature 依然是将Zuul标识为Discovery模式.
    @Override
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Discovery)", ZuulProxyConfiguration.class);
    }

    @Bean
    @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
    public DiscoveryClientRouteLocator discoveryRouteLocator() {
        return new DiscoveryClientRouteLocator(this.server.getServletPrefix(), this.discovery, this.zuulProperties,
                this.serviceRouteMapper);
    }

    // 依然是注册了这么个ApplicationEvent来触发上文中的dirty状态.
    @Bean
    public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {
        return new ZuulDiscoveryRefreshListener();
    }

    private static class ZuulDiscoveryRefreshListener implements ApplicationListener<ApplicationEvent> {

        private HeartbeatMonitor monitor = new HeartbeatMonitor();

        @Autowired
        private ZuulHandlerMapping zuulHandlerMapping;

        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if (event instanceof InstanceRegisteredEvent) {
                reset();
            }
            else if (event instanceof ParentHeartbeatEvent) {
                ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
                resetIfNeeded(e.getValue());
            }
            else if (event instanceof HeartbeatEvent) {
                HeartbeatEvent e = (HeartbeatEvent) event;
                resetIfNeeded(e.getValue());
            }

        }

        private void resetIfNeeded(Object value) {
            if (this.monitor.update(value)) {
                reset();
            }
        }

        private void reset() {
            this.zuulHandlerMapping.setDirty(true);
        }

    }

    //其余省略
}

DiscoveryClientRouteLocator类中的locateRoutes()方法,将path与上文的ZuulRoute通过DiscoveryClientRouteLocator.locateRoutes()的对应在一起.
点击查看其父类,org.springframework.cloud.netflix.zuul.ZuulConfiguration,如下我们可以看到Netflix的Zuul-core的入口,ZuulServlet。

1.4 DiscoveryClientRouteLocator中locateRoutes

DiscoveryClientRouteLocator类中的locateRoutes的大概流程

  1. 将上文SimpleRouteLocator中解析出来的Route列表灌入内部的LinkedHashMap
  2. 抽取Route自带的serviceId,将其作为key,形成一个staticServices的map
  3. 遍历DiscoveryClient拿到的serviceId列表,匹配正则形式定义的serviceId并将对应的ZuulRoute与之对应
  4. 调整LinkedHashMap内路由顺序,将/**挪到最后
  5. 微调map内容,将key值加上/或者自定义prefix

1.5 ZuulConfiguration

@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulConfiguration {

    //zuulProperties 对应配置文件的内容
    @Autowired
    protected ZuulProperties zuulProperties;

    @Autowired
    protected ServerProperties server;

    @Autowired(required = false)
    private ErrorController errorController;

    //告知actuator监控当前模式:Simple/Discovery
    @Override
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Discovery)", ZuulProxyConfiguration.class);
    }

   //通过继承ServletWrappingController接管了上文定义的ZuulServlet,因此ZuulController就是Zuul的入口
   @Bean
    public ZuulController zuulController() {
        return new ZuulController();
    }

   /**
    *ZuulHandlerMapping,响应器模式,其实目前就是把所有路径的请求导入到ZuulController上.</br>
    *另外的功效是当觉察RouteLocator路由表变更,则更新自己dirty状态,重新注册所有Route到ZuulController.
    */
   @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        return mapping;
    }

   /**
   * ZuulRefreshListener,
   */
   private static class ZuulRefreshListener
            implements ApplicationListener<ApplicationEvent> {

        @Autowired
        private ZuulHandlerMapping zuulHandlerMapping;

        private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            // Simple模式下注册RoutesRefreshedEvent,解析配置文件,
            // 维护路由表并监听变化,将请求都导向ZuulController去历经filters
            if (event instanceof ContextRefreshedEvent
                    || event instanceof RefreshScopeRefreshedEvent
                    || event instanceof RoutesRefreshedEvent) {
                this.zuulHandlerMapping.setDirty(true);
            } 
            // Endpoint模式下又添加了HeartbeatEvent
            else if (event instanceof HeartbeatEvent) {
                if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
                    this.zuulHandlerMapping.setDirty(true);
                }
            }
        }

    }
   //其余省略
}

从ZuulConfiguration中可以拿到Simple模式下所有bean.

1.6 ZuulController整合访问的桥梁

ZuulController继承了ServletWrappingController,将当前应用中的某个Servlet直接包装为一个Controller,所有到ServletWrappingController的请求实际上是由它内部所包装的这个Servlet 实例来处理的,也就是说内部封装的Servlet实例并不对外开放,对于程序的其他范围是不可见的,适配所有的HTTP请求到内部封装的Servlet实例进行处理。它通常用于对已存的Servlet的逻辑重用上。其实这也就是Spring Cloud与Netflix Zuul整合的关键点。

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();
        }
    }

}

1.7 ZuulProperties

zuul.ignoredServices
zuul.routes

zuul:
  ignored-services:
  routes:

其中routes对应着内部类定义ZuulRoute.

1.8 其它补充说明

org.springframework.cloud.netflix.zuul.filters.Route,是Spring Cloud 的抽象,就是上文RouteLocator潜移默化转换的部分.

org.springframework.cloud.netflix.zuul.ZuulFilterInitializer,实现ServletContextListener,servlet内容来自tomcat。