一彪标、簡要介紹
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.
這里有幾個關(guān)鍵詞
- front door
我們這里可以理解為請求網(wǎng)關(guān)他爸,在底層服務(wù)和客戶端/網(wǎng)頁之間的通訊橋梁。我個人對zuul的理解更偏向于業(yè)務(wù)總線,服務(wù)編排冰垄,整合底層的各種基礎(chǔ)服務(wù)滑绒。 - dynamic routing
動態(tài)路由 - resiliency
可伸縮 - security
安全性
wiki里面提到的3個使用場景 - Surgical Routing
- Stress Testing
- Multi-Region Resiliency
其實我個人認為有點夸大的成分,我理解這些都是動態(tài)路由的應(yīng)用場景而已骚勘,而zuul的動態(tài)路由也只是說把攔截器的邏輯用groovy來寫铐伴,通過這種方式來實現(xiàn)熱部署撮奏,并不是什么稀奇的東西。
二当宴、源碼下載&啟動
- 源碼下載并編譯
- 修改配置文件
application.properties
zuul.filters.root=zuul-sample/src/main/groovy/com/netflix/zuul/sample/filters
filter的根目錄修改一下
- 進程啟動配置jvm的系統(tǒng)參數(shù)
-DTZ=GMT -Darchaius.deployment.environment=test -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false -Deureka.validateInstanceId=false -Deureka.mt.num_retries=1
- 運行com.netflix.zuul.sample.Bootstrap啟動類
Zuul Sample: starting up.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Zuul Sample: finished startup. Duration = 26856 ms
2019-02-15 00:38:59,487 WARN io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'SO_KEEPALIVE' for channel '[id: 0x283bb17c]'
2019-02-15 00:38:59,487 WARN io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'SO_LINGER' for channel '[id: 0x283bb17c]'
2019-02-15 00:38:59,487 WARN io.netty.bootstrap.ServerBootstrap [main] Unknown channel option 'TCP_NODELAY' for channel '[id: 0x283bb17c]'
三畜吊、核心組件分析
這里對zuul分析的版本是v1.1.0。為什么選擇zuul1的版本來分析户矢,主要是考慮到第一版的特性會比較少玲献,方便我了解zuul的核心功能。另外zuul1用的是BIO梯浪,代碼看起來會簡單不少捌年。zuul2增加了很多新的特性和優(yōu)化,后面會抽時間慢慢看完挂洛。
接下來分析下zuul-simple-webapp
web.xml configures a few things:
-
StartServer as a
ServletContextListener
that initializes the app. - ZuulServlet is a servlet that matches all requests. It performs the core Zuul Filter flow of executing pre, routing, and post Filters.
-
ContextLifecycleFilter is a servlet filter matching all requests. It cleans up the
RequestContext
after each request, ensuring isolation.
<listener>
<listener-class>com.netflix.zuul.StartServer</listener-class>
</listener>
<servlet>
<servlet-name>Zuul</servlet-name>
<servlet-class>com.netflix.zuul.http.ZuulServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Zuul</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<filter>
<filter-name>ContextLifecycleFilter</filter-name>
<filter-class>com.netflix.zuul.context.ContextLifecycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ContextLifecycleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1. StartServer.java
private void initGroovyFilterManager() {
FilterLoader.getInstance().setCompiler(new GroovyCompiler());
String scriptRoot = System.getProperty("zuul.filter.root", "");
if (scriptRoot.length() > 0) scriptRoot = scriptRoot + File.separator;
try {
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(5, scriptRoot + "pre", scriptRoot + "route", scriptRoot + "post");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
這里定義了groovy的編譯器和Zuul的Filter的加載路徑礼预。
2. ZuulFilter的定義
zuulFilter即為zuul的過濾器實現(xiàn)。我們后續(xù)需要實現(xiàn)的各種過濾器都必須基于這個類來實現(xiàn)虏劲。接口比較簡單托酸,注釋也寫得很好,這里就不展開來講伙单。
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final DynamicBooleanProperty filterDisabled =
DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);
/**
* to classify a filter by type. Standard types in Zuul are "pre" for pre-routing filtering,
* "route" for routing to an origin, "post" for post-routing filters, "error" for error handling.
* We also support a "static" type for static responses see StaticResponseFilter.
* Any filterType made be created or added and run by calling FilterProcessor.runFilters(type)
*
* @return A String representing that type
*/
abstract public String filterType();
/**
* filterOrder() must also be defined for a filter. Filters may have the same filterOrder if precedence is not
* important for a filter. filterOrders do not need to be sequential.
*
* @return the int order of a filter
*/
abstract public int filterOrder();
/**
* By default ZuulFilters are static; they don't carry state. This may be overridden by overriding the isStaticFilter() property to false
*
* @return true by default
*/
public boolean isStaticFilter() {
return true;
}
/**
* The name of the Archaius property to disable this filter. by default it is zuul.[classname].[filtertype].disable
*
* @return
*/
public String disablePropertyName() {
return "zuul." + this.getClass().getSimpleName() + "." + filterType() + ".disable";
}
/**
* If true, the filter has been disabled by archaius and will not be run
*
* @return
*/
public boolean isFilterDisabled() {
return filterDisabled.get();
}
3. ZuulServlet.java
接下來看ZuulServlet,這部分是zuul最核心的邏輯获高。
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
String bufferReqsStr = config.getInitParameter("buffer-requests");
boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true") ? true : false;
zuulRunner = new ZuulRunner(bufferReqs);
}
首先會初始化ZuulRunner,其中bufferReqs這個屬性其實就是控制Request是否使用bufferReader吻育,允許多次從request里面讀取內(nèi)容念秧。
ZuulRunner.java
/**
* sets HttpServlet request and HttpResponse
*
* @param servletRequest
* @param servletResponse
*/
public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
RequestContext ctx = RequestContext.getCurrentContext();
if (bufferRequests) {
ctx.setRequest(new HttpServletRequestWrapper(servletRequest));
} else {
ctx.setRequest(servletRequest);
}
ctx.setResponse(new HttpServletResponseWrapper(servletResponse));
}
接下里我們看zuulSerlvet的核心邏輯。
@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();
}
}
- preRoute和postRoute不管發(fā)生任何異常的情況下都會執(zhí)行布疼。如果在執(zhí)行
preRoute的時候就發(fā)生異常摊趾,會跳過route的邏輯,直接到postRoute游两。 - 不管在哪一步發(fā)生異常都會執(zhí)行error的過濾器砾层。
另外RequestContext的作用為了保存請求和執(zhí)行結(jié)果的上下文,方便在過濾器中傳遞贱案。包括過濾器之間如果需要做傳輸傳遞的話也是依賴RequestContext來實現(xiàn)肛炮。
5.FilterProcessor.java
最后我們再看下執(zhí)行過濾器的邏輯,實時上就是根據(jù)過濾器的類型拿到過濾器的鏈表宝踪,遍歷執(zhí)行
/**
* runs all filters of the filterType sType/ Use this method within filters to run custom filters by type
*
* @param sType the filterType.
* @return
* @throws Throwable throws up an arbitrary exception
*/
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
三侨糟、總結(jié)
zuul的代碼不多,整體的解決思路有點類似spring的攔截器實現(xiàn)瘩燥,只是spring攔截器一般面向的是方法秕重,zuul建議面向的是服務(wù)(當(dāng)然這個看個人的使用方式)。另外zuul使用允許過濾器使用groovy進行動態(tài)編譯注入厉膀,不需要發(fā)版溶耘。我認為在解決問題的思路上并不是一個新的思路二拐,只是說在分布式的場景下的一個應(yīng)用場景罷了。當(dāng)然zuul2增加了很多新的特性凳兵,這個是需要我這邊去深入了解的百新。