Spring Cloud Zuul详解

Spring Cloud对Netflix Zuul做了封装集成, 使得在Spring Cloud环境中使用Zuul更方便. Netflix Zuul相关分析请看上一篇.

实现

@EnableZuulProxy 与 @EnableZuulServer 二者的区别在于前者使用了服务发现作为路由寻址, 并使用Ribbon做客户端的负载均衡; 后者没有使用. Zuul server的路由都通过ZuulProperties进行配置.

具体实现:

  1. 使用ZuulController(ServletWrappingController的子类)封装ZuulServlet实例, 处理从DispatcherServlet进来的请求.
  2. ZuulHandlerMapping负责注册handler mapping, 将RoutefullPath的请求交由ZuulController处理.
  3. 同时使用ServletRegistrationBean注册ZuulServlet, 默认使用/zuul作为urlMapping. 所有来自以/zuul开头的path的请求都会直接进入ZuulServlet, 不会进入DispatcherServlet.

使用注解

  • @EnableZuulProxy引入了ZuulProxyMarkerConfiguration, ZuulProxyMarkerConfiguration只做了一件事, 实例化了内部类Marker.

    @Configuration
    public class ZuulProxyMarkerConfiguration {
        @Bean
        public Marker zuulProxyMarkerBean() {
            return new Marker();
        }
    
        class Marker {
        }
    }
    
  • @EnableZuulServer引入了ZuulServerMarkerConfiguration, ZuulServerMarkerConfiguration也只做了一件事: 实例化了内部类Marker

    @Configuration
    public class ZuulServerMarkerConfiguration {
        @Bean
        public Marker zuulServerMarkerBean() {
            return new Marker();
        }
    
        class Marker {
        }
    }
    

EnableAutoConfiguration

项目中使用@EnableAutoConfiguration注解, 开启Spring上下文对象的自动配置功能, 尝试去猜测和实例化你可能需要的bean.

这个功能是基于classPath来完成的. 比如: 项目中引用了tomcat-embedded.jar, 你可能需要一个TomcatEmbeddedServletContainerFactory实例, 除非定义了自己的EmbeddedServletContainerFactory实例.

我们来接着看, 在spring-cloud-netflix-corespring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration实现中我们可以找到org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfigurationorg.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration

  • ZuulServerAutoConfiguration 它的初始化条件有两个:

    • @ConditionalOnClass(ZuulServlet.class)指定classpath中需要有ZuulServlet.class. 这个servlet负责对所有进入Zuul server的请求以及配置应用指定的preRoute, route, postRouteerror.
    • @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)@EnableZuulServer注解呼应.

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

  • ZuulProxyAutoConfiguration 它有一个初始化的条件@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class), 就是上下文中需要有ZuulProxyMarkerConfiguration.Marker这个内部类的bean. 与@EnableZuulProxy注解呼应.

    初始化包括内置的filter, 以及Discovery, Ribbon等的初始化.

    @Configuration
    @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class })
    @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
    public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
        ...
    }
    

ZuulServerAutoConfiguration 详解
//声明配置
@Configuration
//配置ZuulProperties实例
@EnableConfigurationProperties({ ZuulProperties.class })
//条件1 存在ZuulServlet.class
@ConditionalOnClass(ZuulServlet.class)
//条件2 存在ZuulServerMarkerConfiguration.Marker.class bean, 即应用使用@EnableZuulServer注解
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
//配置ServerProperties实例
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import(ServerPropertiesAutoConfiguration.class)
public class ZuulServerAutoConfiguration {

	@Autowired
	protected ZuulProperties zuulProperties;

	@Autowired
	protected ServerProperties server;

	@Autowired(required = false)
	private ErrorController errorController;

	@Bean
	public HasFeatures zuulFeature() {
		return HasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
	}

  //复合结构的RouteLocator
	@Bean
	@Primary
	public CompositeRouteLocator primaryRouteLocator(
			Collection<RouteLocator> routeLocators) {
		return new CompositeRouteLocator(routeLocators);
	}

  //没有SimpleRouteLocator.class的bean时, 使用zuulProperties实例化一个SimpleRouteLocator实例.
	@Bean
	@ConditionalOnMissingBean(SimpleRouteLocator.class)
	public SimpleRouteLocator simpleRouteLocator() {
		return new SimpleRouteLocator(this.server.getServletPrefix(),
				this.zuulProperties);
	}
  
  //zuulController, 包装了一个ZuulServlet类型的servlet, 实现对ZuulServlet类型的servlet的初始化.
	@Bean
	public ZuulController zuulController() {
		return new ZuulController();
	}

	@Bean
	public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
		ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
		mapping.setErrorController(this.errorController);
		return mapping;
	}

	@Bean
	public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
		return new ZuulRefreshListener();
	}

	@Bean
	@ConditionalOnMissingBean(name = "zuulServlet")
	public ServletRegistrationBean zuulServlet() {
		ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),
				this.zuulProperties.getServletPattern());
		// The whole point of exposing this servlet is to provide a route that doesn't
		// buffer requests.
		servlet.addInitParameter("buffer-requests", "false");
		return servlet;
	}

	// pre filters

	@Bean
	public ServletDetectionFilter servletDetectionFilter() {
		return new ServletDetectionFilter();
	}

	@Bean
	public FormBodyWrapperFilter formBodyWrapperFilter() {
		return new FormBodyWrapperFilter();
	}

	@Bean
	public DebugFilter debugFilter() {
		return new DebugFilter();
	}

	@Bean
	public Servlet30WrapperFilter servlet30WrapperFilter() {
		return new Servlet30WrapperFilter();
	}

	// post filters

	@Bean
	public SendResponseFilter sendResponseFilter() {
		return new SendResponseFilter();
	}

	@Bean
	public SendErrorFilter sendErrorFilter() {
		return new SendErrorFilter();
	}

	@Bean
	public SendForwardFilter sendForwardFilter() {
		return new SendForwardFilter();
	}

	@Bean
	@ConditionalOnProperty(value = "zuul.ribbon.eager-load.enabled", matchIfMissing = false)
	public ZuulRouteApplicationContextInitializer zuulRoutesApplicationContextInitiazer(
			SpringClientFactory springClientFactory) {
		return new ZuulRouteApplicationContextInitializer(springClientFactory,
				zuulProperties);
	}

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

	}

	@Configuration
	@ConditionalOnClass(CounterService.class)
	protected static class ZuulCounterFactoryConfiguration {

		@Bean
		@ConditionalOnBean(CounterService.class)
		public CounterFactory counterFactory(CounterService counterService) {
			return new DefaultCounterFactory(counterService);
		}
	}

	@Configuration
	protected static class ZuulMetricsConfiguration {

		@Bean
		@ConditionalOnMissingBean(CounterFactory.class)
		public CounterFactory counterFactory() {
			return new EmptyCounterFactory();
		}

		@ConditionalOnMissingBean(TracerFactory.class)
		@Bean
		public TracerFactory tracerFactory() {
			return new EmptyTracerFactory();
		}

	}

	private static class ZuulRefreshListener
			implements ApplicationListener<ApplicationEvent> {

		@Autowired
		private ZuulHandlerMapping zuulHandlerMapping;

		private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

		@Override
		public void onApplicationEvent(ApplicationEvent event) {
			if (event instanceof ContextRefreshedEvent
					|| event instanceof RefreshScopeRefreshedEvent
					|| event instanceof RoutesRefreshedEvent) {
				this.zuulHandlerMapping.setDirty(true);
			}
			else if (event instanceof HeartbeatEvent) {
				if (this.heartbeatMonitor.update(((HeartbeatEvent) event).getValue())) {
					this.zuulHandlerMapping.setDirty(true);
				}
			}
		}

	}

}
ZuulProxyAutoConfiguration 详解
​```java
//声明配置
@Configuration
//引入RibbonCommandFactory配置
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
        RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
        HttpClientConfiguration.class })
//配置生效条件
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {

    @SuppressWarnings("rawtypes")
    @Autowired(required = false)
    private List<RibbonRequestCustomizer> requestCustomizers = Collections.emptyList();
  //网关服务注册实例信息
    @Autowired(required = false)
    private Registration registration;
  //服务发现客户端
    @Autowired
    private DiscoveryClient discovery;
  //serviceId和路由的映射逻辑, 默认为相同
    @Autowired
    private ServiceRouteMapper serviceRouteMapper;

    @Override
    public HasFeatures zuulFeature() {
        return HasFeatures.namedFeature("Zuul (Discovery)",
                ZuulProxyAutoConfiguration.class);
    }

  //静态和动态路由寻址: 静态从配置文件获取, 动态通过服务发现客户端完成. 后者优先级更高
    @Bean
    @ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
    public DiscoveryClientRouteLocator discoveryRouteLocator() {
        return new DiscoveryClientRouteLocator(this.server.getServletPrefix(),
                this.discovery, this.zuulProperties, this.serviceRouteMapper, this.registration);
    }

  //装饰过滤器
    // pre filters
    @Bean
    public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
            ProxyRequestHelper proxyRequestHelper) {
        return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
                this.zuulProperties, proxyRequestHelper);
    }
  
  //基于Ribbon路由过滤器
    // route filters
    @Bean
    public RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper,
            RibbonCommandFactory<?> ribbonCommandFactory) {
        RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory,
                this.requestCustomizers);
        return filter;
    }

  //基于host的路由过滤器
    @Bean
    @ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})
    public SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper,
            ZuulProperties zuulProperties,
            ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
            ApacheHttpClientFactory httpClientFactory) {
        return new SimpleHostRoutingFilter(helper, zuulProperties,
                connectionManagerFactory, httpClientFactory);
    }

    @Bean
    @ConditionalOnMissingBean({SimpleHostRoutingFilter.class})
    public SimpleHostRoutingFilter simpleHostRoutingFilter2(ProxyRequestHelper helper,
                                                           ZuulProperties zuulProperties,
                                                           CloseableHttpClient httpClient) {
        return new SimpleHostRoutingFilter(helper, zuulProperties,
                httpClient);
    }

  //服务发现寻址刷新监听器
    @Bean
    public ApplicationListener<ApplicationEvent> zuulDiscoveryRefreshRoutesListener() {
        return new ZuulDiscoveryRefreshListener();
    }

    @Bean
    @ConditionalOnMissingBean(ServiceRouteMapper.class)
    public ServiceRouteMapper serviceRouteMapper() {
        return new SimpleServiceRouteMapper();
    }

    @Configuration
    @ConditionalOnMissingClass("org.springframework.boot.actuate.endpoint.Endpoint")
    protected static class NoActuatorConfiguration {

        @Bean
        public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
            ProxyRequestHelper helper = new ProxyRequestHelper();
            helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders());
            helper.setTraceRequestBody(zuulProperties.isTraceRequestBody());
            return helper;
        }

    }

    @Configuration
    @ConditionalOnClass(Endpoint.class)
    protected static class EndpointConfiguration {

        @Autowired(required = false)
        private TraceRepository traces;

        @ConditionalOnEnabledEndpoint("routes")
        @Bean
        public RoutesEndpoint routesEndpoint(RouteLocator routeLocator) {
            return new RoutesEndpoint(routeLocator);
        }

        @ConditionalOnEnabledEndpoint("routes")
        @Bean
        public RoutesMvcEndpoint routesMvcEndpoint(RouteLocator routeLocator,
                RoutesEndpoint endpoint) {
            return new RoutesMvcEndpoint(endpoint, routeLocator);
        }

        @ConditionalOnEnabledEndpoint("filters")
        @Bean
        public FiltersEndpoint filtersEndpoint() {
            FilterRegistry filterRegistry = FilterRegistry.instance();
            return new FiltersEndpoint(filterRegistry);
        }

        @Bean
        public ProxyRequestHelper proxyRequestHelper(ZuulProperties zuulProperties) {
            TraceProxyRequestHelper helper = new TraceProxyRequestHelper();
            if (this.traces != null) {
                helper.setTraces(this.traces);
            }
            helper.setIgnoredHeaders(zuulProperties.getIgnoredHeaders());
            helper.setTraceRequestBody(zuulProperties.isTraceRequestBody());
            return helper;
        }
    }

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

    }

}
​```

配置项

zuul.servletPath

默认为*/zuul*, 注册ZuulServlet的时候作为urlMapping使用. 即所有来自以*/zuul*开头的path都会由ZuulServlet处理.

zuul.ignoredPatterns

Zuul使用ZuulController封装了ZuulServlet. 所有进入Zuul的请求的入口都是ZuulController. ZuulControllerZuulHandlerMapping默认把zuul.routes.[ITEM].path的请求交给ZuulServlet处理. 如果找不到对应的path的route, 则会走其他的DispatcherServlet

zuul.ignoredPatterns作用就是进入Zuul的请求, 只要match都会直接交由其他的DispatcherServlet处理, 而不需要先检查是否有对应path的route.

过滤器

ZuulServerAutoConfiguration

ServletDetectionFilter

检查请求的入口是DispatcherServlet还是ZuulServlet 如果是DispatcherServlet进来的请求, 将RequestContext中的属性isDispatcherServletRequest设置为ture.

检查的方法是判断RequestContext中的请求类型是否为HttpServletRequestWrapper类型, 因为ZuulServlet进来的请求会使用HttpServletRequestWrapper进行再次封装; 同时检查请求中中是否有DispatcherServlet.CONTEXT属性, 因为DispatcherServlet进来的请求会带有该属性.

FormBodyWrapperFilter

为下游的服务解析表单数据, 并重新编码. 只针对multipart/form-data和application/x-www-form-urlencoded类型的请求.

DebugFilter

通过设置zuul.debug.parameter属性控制, 默认启用. 执行时将上下文中的debugRoutingdebugRequest设置为true

Servlet30WrapperFilter

使用Servlet30RequestWrapper封装请求, 强制启用.

SendResponseFilter

后执行的过滤器, 负责将代理请求的响应写入当前的请求的响应中.

ZuulProxyAutoConfiguration

PreDecorationFilter

Pre类型的过滤器, 通过提供的RouteLocator决定将如何请求路由到哪里和如何路由. 同时为下游请求添加多个与代理相关的头信息. 当RequestContext中不存在FORWARD_TO_KEYSERVICE_ID_KEY信息时生效.

将路由判断结果写入routeHost, FORWARD_TO_KEY或者SERVICE_ID_KEY.

RibbonRoutingFilter

Route类型的过滤器, 当RequestContextrouteHost为空, 且有serviceId值时生效.

使用RequestContext构建RibbonCommandContext, 通过RibbonCommandFactory进而创建RibbonCommand并执行. 最后通过ProxyRequestHelper将响应结果记录到RequestContext中.

SimpleHostRoutingFilter

Route类型的过滤器, 当RequestContext中的routeHost不为空时生效. 使用Apache的HttpClient发送请求

监听器

ZuulRefreshListener

通过监听应用程序事件(ContextRefreshedEvent, RefreshScopeRefreshedEvent, RoutesRefreshedEventRoutesRefreshedEvent)更新handler mapping的注册信息. 前两个事件在ContextRefresh时发出; 第三个是通过JMX重置路由时发出(参考RoutesMvcEndpoint); 最后一个是DiscoveryClient每次拉取服务注册信息后发出.

收到事件后, 将ZuulHandlerMappingdirty变量置为true, 当下次请求进来时, 检查到dirtytrue, 就会重新注册url mapping.

ZuulDiscoveryRefreshListener

监听应用程序事件(InstanceRegisteredEvent, ParentHeartbeatEventHeartbeatEvent)更新handler mapping的注册信息.

InstanceRegisteredEvent当前路由服务实例完成服务注册后发出的事件. ParentHeartbeatEventDiscoveryClient定位到Config Server服务的时候有bootstrapContext发给应用程序上下文的事件. HeartbeatEventDiscoveryClient每次拉取服务注册信息后发出.


文章同步发送到公众号:云编码 (微信号:sevenfeet)。

qrcode

comments powered by Disqus