來源:
《微服務架構實戰160講》
https://www.jianshu.com/p/d1e61f9fc13a?utm_source=oschina-app
API網關的基本功能:
Zuul網關
1 場景
Zuul實現了跨區高可用、防爬防攻擊、健康檢查、屏蔽壞節點、流量判斷等諸多功能
- API網關
類似單體應用中的過濾器,在請求到達業務服務器之前可以做一些操作
- 紅綠部署
類似配置中心Apollo可以實現的藍綠部署(流量判斷)
- 開發者測試分支
也是通過流量判斷,使部分流量達到測試代碼而不是全部
- 埋點測試
也是通過流量判斷,使部分流量達到埋點代碼而不是全部
- 壓力測試
也是通過流量判斷
- 調試路由
也是通過流量判斷,將代碼放到線上來調試
- 金絲雀測試
也是通過流量判斷,部分流量測試金絲雀代碼,如果沒問題,再全量切換
- 粘性金絲雀
同金絲雀測試,不過固定了用戶,如果第一次到達了金絲雀代碼,下一次就依然會到達此處,而不是隨機分配
- 失敗注入測試
注入錯誤,看看業務服務器的容錯性
- 降級測試
網關檢測到錯誤後,能執行降級,將流量打向降級的集羣,可以跟失敗注入測試結合來測試降級能力
2 架構設計+源碼簡析(1.0)
Zuul網關的核心是一系列的過濾器,可以對請求或者響應結果做一系列過濾,在zuul中過濾器分爲四種類型(Type),每一種Type中可以有多個攔截器,開發人員可自行指定執行順序(ExecutionOrder),只要滿足指定的條件(Criteria),就執行指定的動作(Action):
1 PRE Filters(前置過濾器) :當請求會路由轉發到具體後端服務器前執行的過濾器,比如鑑權過濾器,日誌過濾器,路由選擇過濾器
2 ROUTING Filters (路由過濾器):該過濾器作用是把請求具體轉發到後端服務器上,一般是通過Apache HttpClient 或者 Netflix Ribbon把請求發送到具體的後端服務器上
3 POST Filters(後置過濾器):當把請求路由到具體後端服務器後執行的過濾器,可以添加標準http 響應頭、收集一些統計數據(比如請求耗時等)、寫入請求結果到請求方等。
4 ERROR Filters(錯誤過濾器):當任何一個其他類型過濾器執行出錯時候執行該過濾器
- 總體架構+原理
開發/運維人員通過管理模塊來管理過濾器,可以設置不同的狀態,如用於金絲雀測試的狀態等,然後存儲到FilterPersister中,如數據庫,需要做一個表出來存儲
FilterPoller會定期掃描FilterPersister(通過ZuulFilterDaoFactory,工廠模式),加載新的過濾器進FilterDirectory,不同類型的過濾器有各自的目錄,FileManager組合了這幾個目錄,使用一個線程掃描FilterDirectory,加載到FilterLoader中,最終送到FilterRunner中
FilterRunner則完成所有請求流程的過濾,是最核心的組件
請求入口:ZuulServlet本質是一個HttpServlet,會攔截所有請求,執行四個過濾器方法,方法內部會將具體實現交給FilterRunner(委派模式),FilterRunner則交給FilterProcessor真正執行對應類型的過濾器(從FilterLoader中取出相應類型的過濾器,使用for循環依次處理,爲責任鏈模式)
過濾器的執行順序:ZuulServlet#service
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
...
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;
}
...
}
RequestContext存儲整個請求的一些數據,使得過濾器之間可以共享數據,且線程安全,即每個請求有各自的局部RequestContext,由所有過濾器共享(其實就是一個請求裏的數據共享)
RequestContext使用ThreadLocal
實現,其本身簡單wrap了ConcurrentHashMap
,核心源碼:
public class RequestContext extends ConcurrentHashMap<String, Object> {
protected static Class<? extends RequestContext> contextClass = RequestContext.class;
//使用泛型,這樣可以通過繼承RequestContext來定製RequestContext
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
@Override
protected RequestContext initialValue() {
try {
return contextClass.newInstance();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
};
//方法內部都是通過ConcurrentHashMap的put和get
public void setRequest(HttpServletRequest request) {
put("request", request);
}
...
public static RequestContext getCurrentContext() {
if (testContext != null) return testContext;
RequestContext context = threadLocal.get();
return context;
}
}
- 請求處理生命週期
3 部署案例
- 整體架構
- 以無線網關爲例
- 如何獲取路由表
使用Eureka作爲中介,Zuul通過集成Eureka客戶端Ribbon就可以自動發現服務的ip+端口了
也可以基於域名或基於Apollo來實現,不過沒有Eureka好,因爲無法實現自發現,即自動化
4 Zuul2
使用了非阻塞異步的模式(Netty),增加了一些功能點,架構做了一些修改
5 Zuul1最佳實踐
- 使用異步 AsyncServlet 來優化連接數
需要自實現
-
使用Apollo配置中心集成動態配置
-
Hystrix熔斷限流
信號量隔離和線程池隔離兩種方式建議使用信號量隔離,如果使用線程池隔離,則每個服務都要配一個線程池,會導致Zuul1上的線程更多,更容易使網關資源被耗盡
-
對連接池進行管理
-
CAT和Hystrix 監控
-
過濾器調試技巧
使用java代碼調試,完成後再改成groovy去上傳
-
網關不要有業務邏輯
-
自助路由 (需定製擴展 )
提供一些工具、文檔,讓前後端開發人員可以自助擴展網關