1.前言
写本篇文章的起因是最近想在业务层面做一个类似网关的应用,把和外部对接的一些相似逻辑抽取到网关中,和具体的业务剥离开来。
在做这件事情之前,发现自己对于网关的理解不是很深,于是便找了业界比较流行的网关框架作一番学习。选取了Spring Cloud全家桶中的Zuul作为样例。
2.Zuul是什么
Zuul is an edge service that provides dynamic routing, monitoring, resiliency, security, and more.
Zuul是Netflix公司开源的一个API网关组件。提供了认证&鉴权、限流、动态路由,监控,弹性,安全、负载均衡、协助单点压测、静态响应等边缘服务的框架。
Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.
以上是在github上的Zuul项目主页截取的一段内容,介绍Zuul是什么。Zuul在Netflix的架构图如下:
3.Zuul怎么用
Zuul相关案例大家可以参考http://www.ityouknow.com/spring-cloud.html中相关的Zuul章节,或者其他相关博客,本文不在赘述。
4.Zuul源码剖析
如上图所示,Zuul内部的整体架构和流程可以用一张图描述清楚。
Zuul主要包含两个流程,一个是请求经过Zuul的执行流程;另一个是Zuul过滤器的加载流程。
Zuul中请求流程大致如下,请求经过ZuulServlet,ZuulServlet持有一个ZuulRunner对象,ZuulRunner会初始化一个RequestContext对象,该对象持有整个请求过程中的上下文数据,被所有过滤器锁共享。ZuulRunner会执行具体的pre、routing、post以及error类型的过滤器,执行完成之后,返回返回具体的HttpResponse对象。
Zuul过滤器加载的大致流程如下,ZuulServerAutoConfiguration中有一个ZuulFilterConfiguration定义,该定义中会注册一个ZuulFilterInitializer的实例。ZuulFilterInitializer初始化一些Zuul组件,比如过滤器,FilterLoader,FilterRegistry。FilterRegistry管理了一个ConcurrentHashMap,用作存储过滤器的,并有一些基本的CURD过滤器的方法。FilterLoader是Zuul的一个核心类。它用来在源码发生变化时编译、载入和校验过滤器,同时它通过类型来持有过滤器。FilterLoader类持有FilterRegistry,FilterFileManager类持有FilterLoader,所以最终是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager开启了轮询机制,定时的去加载过滤器。FilterFileManager管理目录和轮询修改和新的Groovy 过滤器,其中的startPoller方法负责轮询。
4.1下面开始探索详细的代码
以下代码基于spring-cloud-netflix-core 1.4.2.RELEASE和zuul-core 1.3.0版本。
ZuulApplication
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
以上是项目开始Zuul的入口,通过@EnableZuulProxy注解开启Zuul。
EnableZuulProxy
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
该类直接关联到ZuulProxyMarkerConfiguration的定义。
ZuulProxyMarkerConfiguration
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
该类负责新增一个标记bean来触发ZuulProxyAutoConfiguration对象。
在ZuulProxyAutoConfiguration申明上可以看到ZuulProxyMarkerConfiguration。
ZuulProxyAutoConfiguration
该类的源码中涉及了一些服务发现、路由相关的对象以及一些bean的注册,我们主要看下该类的父类。
ZuulServerAutoConfiguration
@Autowired
protected ZuulProperties zuulProperties;
根据约定规则读取配置。
@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;
}
ZuulServlet的bean注册。
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
注册路由刷新的监听器bean.
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);
}
}
}
}
监听器的类定义。
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
过滤器的bean注册。
@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);
}
}
本段代码比较关键,引入了过滤器加载的流程。
ZuulFilterInitializer
初始化一些Zuul组件,比如过滤器,FilterLoader,FilterRegistry。
FilterRegistry
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
管理了一个ConcurrentHashMap,用作存储过滤器的,并有一些基本的CURD过滤器的方法。
FilterLoader
这是Zuul的一个核心类。它用来在源码变化时编译、载入和校验过滤器。同时它通过类型来持有过滤器。
FilterLoader类持有FilterRegistry,FilterFileManager类持有FilterLoader,所以最终是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager开启了轮询机制,定时的去加载过滤器,
public boolean putFilter(File file) throws Exception {
String sName = file.getAbsolutePath() + file.getName();
if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
LOG.debug("reloading filter " + sName);
filterRegistry.remove(sName);
}
ZuulFilter filter = filterRegistry.get(sName);
if (filter == null) {
Class clazz = COMPILER.compile(file);
if (!Modifier.isAbstract(clazz.getModifiers())) {
filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
if (list != null) {
hashFiltersByType.remove(filter.filterType()); //rebuild this list
}
filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
filterClassLastModified.put(sName, file.lastModified());
return true;
}
}
return false;
}
以上是从File对象中读取过滤器的定义,并将过滤器的实例放入filterRegistry中管理。
FilterFileManager
这个类管理目录和轮询修改和新的Groovy 过滤器。轮询的间隔和目录在类初始化时候指定,一个轮询器会检查修改和新增。
以下是轮询过滤器的的代码:
void startPoller() {
poller = new Thread("GroovyFilterFileManagerPoller") {
public void run() {
while (bRunning) {
try {
sleep(pollingIntervalSeconds * 1000);
manageFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
poller.setDaemon(true);
poller.start();
}
Zuulservlet
除了过滤器加载的流程,另外一个重要的流程是请求执行的流程,详细的代码逻辑如下。
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
init方法用来初始化给Request和Response对象做封装,然后是具体的过滤器执行逻辑,依次是pre、route、post过滤器,如果执行过程中抛出异常会执行error过滤器。
ZuulFilter
Zuul中所有的过滤器都会继承ZuulFilter抽象类,该类中有4个主要方法。
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();
Object run();
filterType:该函数需要返回一个字符串来代表过滤器的类型,而这个类型就是在HTTP请求过程中定义的各个阶段。在Zuul中默认定义了四种不同生命周期的过滤器类型,具体如下:
pre:可以在请求被路由之前调用。
routing:在路由请求时候被调用。
post:在routing和error过滤器之后被调用。
error:处理请求时发生错误时被调用。
filterOrder:通过int值来定义过滤器的执行顺序,数值越小优先级越高。数值可以重复。
shouldFilter:返回一个boolean类型来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围。isFilterDisabled也会做判断。
run:过滤器的具体逻辑。在该函数中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。