1.前言
寫(xiě)本篇文章的起因是最近想在業(yè)務(wù)層面做一個(gè)類似網(wǎng)關(guān)的應(yīng)用,把和外部對(duì)接的一些相似邏輯抽取到網(wǎng)關(guān)中,和具體的業(yè)務(wù)剝離開(kāi)來(lái)。
在做這件事情之前,發(fā)現(xiàn)自己對(duì)于網(wǎng)關(guān)的理解不是很深,于是便找了業(yè)界比較流行的網(wǎng)關(guān)框架作一番學(xué)習(xí)。選取了Spring Cloud全家桶中的Zuul作為樣例。
2.Zuul是什么
Zuul is an edge service that provides dynamic routing, monitoring, resiliency, security, and more.
Zuul是Netflix公司開(kāi)源的一個(gè)API網(wǎng)關(guān)組件。提供了認(rèn)證&鑒權(quán)、限流、動(dòng)態(tài)路由,監(jiān)控,彈性,安全、負(fù)載均衡、協(xié)助單點(diǎn)壓測(cè)、靜態(tài)響應(yīng)等邊緣服務(wù)的框架。
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項(xiàng)目主頁(yè)截取的一段內(nèi)容,介紹Zuul是什么。Zuul在Netflix的架構(gòu)圖如下:

3.Zuul怎么用
Zuul相關(guān)案例大家可以參考http://www.ityouknow.com/spring-cloud.html中相關(guān)的Zuul章節(jié),或者其他相關(guān)博客,本文不在贅述。
4.Zuul源碼剖析

如上圖所示,Zuul內(nèi)部的整體架構(gòu)和流程可以用一張圖描述清楚。
Zuul主要包含兩個(gè)流程,一個(gè)是請(qǐng)求經(jīng)過(guò)Zuul的執(zhí)行流程;另一個(gè)是Zuul過(guò)濾器的加載流程。
Zuul中請(qǐng)求流程大致如下,請(qǐng)求經(jīng)過(guò)ZuulServlet,ZuulServlet持有一個(gè)ZuulRunner對(duì)象,ZuulRunner會(huì)初始化一個(gè)RequestContext對(duì)象,該對(duì)象持有整個(gè)請(qǐng)求過(guò)程中的上下文數(shù)據(jù),被所有過(guò)濾器鎖共享。ZuulRunner會(huì)執(zhí)行具體的pre、routing、post以及error類型的過(guò)濾器,執(zhí)行完成之后,返回返回具體的HttpResponse對(duì)象。
Zuul過(guò)濾器加載的大致流程如下,ZuulServerAutoConfiguration中有一個(gè)ZuulFilterConfiguration定義,該定義中會(huì)注冊(cè)一個(gè)ZuulFilterInitializer的實(shí)例。ZuulFilterInitializer初始化一些Zuul組件,比如過(guò)濾器,F(xiàn)ilterLoader,F(xiàn)ilterRegistry。FilterRegistry管理了一個(gè)ConcurrentHashMap,用作存儲(chǔ)過(guò)濾器的,并有一些基本的CURD過(guò)濾器的方法。FilterLoader是Zuul的一個(gè)核心類。它用來(lái)在源碼發(fā)生變化時(shí)編譯、載入和校驗(yàn)過(guò)濾器,同時(shí)它通過(guò)類型來(lái)持有過(guò)濾器。FilterLoader類持有FilterRegistry,F(xiàn)ilterFileManager類持有FilterLoader,所以最終是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager開(kāi)啟了輪詢機(jī)制,定時(shí)的去加載過(guò)濾器。FilterFileManager管理目錄和輪詢修改和新的Groovy 過(guò)濾器,其中的startPoller方法負(fù)責(zé)輪詢。
4.1下面開(kāi)始探索詳細(xì)的代碼
以下代碼基于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);
}
}
以上是項(xiàng)目開(kāi)始Zuul的入口,通過(guò)@EnableZuulProxy注解開(kāi)啟Zuul。
EnableZuulProxy
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
該類直接關(guān)聯(lián)到ZuulProxyMarkerConfiguration的定義。
ZuulProxyMarkerConfiguration
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
該類負(fù)責(zé)新增一個(gè)標(biāo)記bean來(lái)觸發(fā)ZuulProxyAutoConfiguration對(duì)象。
在ZuulProxyAutoConfiguration申明上可以看到ZuulProxyMarkerConfiguration。
ZuulProxyAutoConfiguration
該類的源碼中涉及了一些服務(wù)發(fā)現(xiàn)、路由相關(guān)的對(duì)象以及一些bean的注冊(cè),我們主要看下該類的父類。
ZuulServerAutoConfiguration
@Autowired
protected ZuulProperties zuulProperties;
根據(jù)約定規(guī)則讀取配置。
@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注冊(cè)。
@Bean
public ApplicationListener<ApplicationEvent> zuulRefreshRoutesListener() {
return new ZuulRefreshListener();
}
注冊(cè)路由刷新的監(jiān)聽(tīng)器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);
}
}
}
}
監(jiān)聽(tīng)器的類定義。
@Bean
public ServletDetectionFilter servletDetectionFilter() {
return new ServletDetectionFilter();
}
過(guò)濾器的bean注冊(cè)。
@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);
}
}
本段代碼比較關(guān)鍵,引入了過(guò)濾器加載的流程。
ZuulFilterInitializer
初始化一些Zuul組件,比如過(guò)濾器,F(xiàn)ilterLoader,F(xiàn)ilterRegistry。
FilterRegistry
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
管理了一個(gè)ConcurrentHashMap,用作存儲(chǔ)過(guò)濾器的,并有一些基本的CURD過(guò)濾器的方法。
FilterLoader
這是Zuul的一個(gè)核心類。它用來(lái)在源碼變化時(shí)編譯、載入和校驗(yàn)過(guò)濾器。同時(shí)它通過(guò)類型來(lái)持有過(guò)濾器。
FilterLoader類持有FilterRegistry,F(xiàn)ilterFileManager類持有FilterLoader,所以最終是由FilterFileManager注入 filterFilterRegistry的ConcurrentHashMap的。FilterFileManager開(kāi)啟了輪詢機(jī)制,定時(shí)的去加載過(guò)濾器,
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對(duì)象中讀取過(guò)濾器的定義,并將過(guò)濾器的實(shí)例放入filterRegistry中管理。
FilterFileManager
這個(gè)類管理目錄和輪詢修改和新的Groovy 過(guò)濾器。輪詢的間隔和目錄在類初始化時(shí)候指定,一個(gè)輪詢器會(huì)檢查修改和新增。
以下是輪詢過(guò)濾器的的代碼:
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
除了過(guò)濾器加載的流程,另外一個(gè)重要的流程是請(qǐng)求執(zhí)行的流程,詳細(xì)的代碼邏輯如下。
@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方法用來(lái)初始化給Request和Response對(duì)象做封裝,然后是具體的過(guò)濾器執(zhí)行邏輯,依次是pre、route、post過(guò)濾器,如果執(zhí)行過(guò)程中拋出異常會(huì)執(zhí)行error過(guò)濾器。
ZuulFilter
Zuul中所有的過(guò)濾器都會(huì)繼承ZuulFilter抽象類,該類中有4個(gè)主要方法。
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();
Object run();
filterType:該函數(shù)需要返回一個(gè)字符串來(lái)代表過(guò)濾器的類型,而這個(gè)類型就是在HTTP請(qǐng)求過(guò)程中定義的各個(gè)階段。在Zuul中默認(rèn)定義了四種不同生命周期的過(guò)濾器類型,具體如下:
pre:可以在請(qǐng)求被路由之前調(diào)用。
routing:在路由請(qǐng)求時(shí)候被調(diào)用。
post:在routing和error過(guò)濾器之后被調(diào)用。
error:處理請(qǐng)求時(shí)發(fā)生錯(cuò)誤時(shí)被調(diào)用。
filterOrder:通過(guò)int值來(lái)定義過(guò)濾器的執(zhí)行順序,數(shù)值越小優(yōu)先級(jí)越高。數(shù)值可以重復(fù)。
shouldFilter:返回一個(gè)boolean類型來(lái)判斷該過(guò)濾器是否要執(zhí)行。我們可以通過(guò)此方法來(lái)指定過(guò)濾器的有效范圍。isFilterDisabled也會(huì)做判斷。
run:過(guò)濾器的具體邏輯。在該函數(shù)中,我們可以實(shí)現(xiàn)自定義的過(guò)濾邏輯,來(lái)確定是否要攔截當(dāng)前的請(qǐng)求,不對(duì)其進(jìn)行后續(xù)的路由,或是在請(qǐng)求路由返回結(jié)果之后,對(duì)處理結(jié)果做一些加工等。