诺诺清蔓

简单,美好,奋发

一个对技术追求的Java萌妹子


Spring Cloud Zuul遗失的世界(一)

摘要: 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两种注解就可以。

1
2
3
4
5
6
7
8
@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,如下所示。

1
2
3
4
5
6
7
@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,新增了服务与实例等概念,核心重要代码已经加入注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@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整合的关键点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
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。

最近的文章

Spring Cloud Zuul遗失的世界(二)

摘要: 接着上一篇《Spring Cloud Zuul遗失的世界(一)》,ZuulController继承了ServletWrappingController,将当前应用中的ZuulServlet直接包装为一个Controller,暴露为入口访问,在本篇文章中介绍etflix-zuul-core的代码Zuul的执行的生命周期等。 一.Netflix zuul core源码分析com.netflix.zuul.http.ZuulServlet是ZuulFilter链执行的入口,通过service方法,提取请求到RequestContext,然后通过调用ZuulRunner,依次按顺序执行每种类型的Filter,完成整个Filter的生命周期,架构图如下所示。 …

Spring Cloud Zuul 继续阅读
更早的文章

Spring Cloud Zuul异常处理

最近看到了一个GitHub issue在讨论如何在post类型的zuul filter中设置response body,其中涉及到异常情况下,如何返回一个自定义的response body。正好我在升级spring-cloud,也想弄清楚,spring-cloud-zuul是如何处理异常情况的,所以就仔细看了看这部分的实现细节,现在做个笔记记录下来。 …

Spring Cloud Zuul 继续阅读