微服務是時下最流行的架構之一,做爲微服務不可或缺的一部分,API網關的做用相當重要。本文將對隨行付微服務的API網關實踐進行介紹。java
咱們知道,在一個微服務系統中,整個系統被劃分爲許多小模塊,客戶端想要調用服務,可能須要維護不少ip+port信息,管理十分複雜。API網關做爲整個系統的統一入口,全部請求由網關接收並路由轉發給內部的微服務。對於客戶端而言,系統至關於一個黑箱,客戶端不須要關心其內部結構。web
隨着業務的發展,服務端可能須要對微服務進行從新劃分等操做,因爲網關將客戶端和具體服務隔離,所以能夠在儘可能不改動客戶端的狀況下進行。網關能夠完成權限驗證、限流、安全、監控、緩存、服務路由、協議轉換、服務編排、灰度發佈等功能剝離出來,講這些非業務功能統一解決、統一機制處理。spring
隨行付微服務API網關基於Netflix的Zuul實現。Netflix是實踐微服務最成功的公司之一,他們建立並開源了一系列微服務相關的框架,Zuul即是用來實現網關功能的框架。Zuul的總體架構圖以下:緩存
Zuul基於Servlet開發,ZuulServlet是整個框架的入口。Zuul的核心組件是Filter,Filter分爲四類,分別是pre、route、post、error。pre-filter用來實現前置邏輯,route-filter用來實現對目標服務的調用邏輯,post-filter用來實現收尾邏輯,error-filter則在任意位置發生異常時作異常處理(此處應該注意,若是pre或route發生異常,執行error後,仍然會執行post),其示意圖以下:安全
在Filter中能夠定義某些條件下是否執行過濾器邏輯,以及同種類Filter的優先級。Filter的各個方法中並不存在入參,其參數傳遞是經過一個基於ThreadLocal實現的RequestContext,雖然RequestContext中定義了不少參數的讀寫方法,但初始的可用參數僅有req和res,對應HttpSerlvetRequest和HttpServletResponse。Filter代碼範例以下:架構
public class TestFilter extends ZuulFilter { @Override /** 是否攔截 */ public boolean shouldFilter() { return false; } @Override /** filter邏輯 */ public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext();// 獲取當前線程的 HttpServletRequest req = context.getRequest();// 獲取請求信息 return null; // 從源碼來看,這個返回值沒什麼用 } @Override /** filter類型 */ public String filterType() { return "pre";// pre/route/post/error } @Override /** filter優先級,僅在同類型filter中生效 */ public int filterOrder() { return 0; } }
Filter一般使用groovy編寫,以便於動態加載。當咱們編寫好一個Filter類後,將其放在指定的磁盤路徑下,FilterFileManager會啓動一個守護線程去按期讀取並加載。經過動態加載,咱們能夠在不停機的狀況下添加、修改功能模塊。FilterFileManager源碼摘要以下:併發
public class FilterFileManager { ... /** * Initialized the GroovyFileManager. * * @throws Exception */ @PostConstruct public void init() throws Exception { long startTime = System.currentTimeMillis(); filterLoader.putFiltersForClasses(config.getClassNames()); manageFiles(); startPoller(); LOG.warn("Finished loading all zuul filters. Duration = " + (System.currentTimeMillis() - startTime) + " ms."); } ... /** 啓動線程定時讀取文件 */ void startPoller() { poller = new Thread("GroovyFilterFileManagerPoller") { public void run() { while (bRunning) { try { sleep(config.getPollingIntervalSeconds() * 1000); manageFiles(); } catch (Exception e) { LOG.error("Error checking and/or loading filter files from Poller thread.", e); } } } }; poller.start(); } ... /** 讀取文件並加載 */ void manageFiles() { try { List<File> aFiles = getFiles(); processGroovyFiles(aFiles); } catch (Exception e) { String msg = "Error updating groovy filters from disk!"; LOG.error(msg, e); throw new RuntimeException(msg, e); } } }
Spring Cloud經過集成Zuul來實現API網關模塊,咱們來簡單介紹一下它的整合原理。app
SpringCloud-Zuul的核心配置類是ZuulServerAutoConfiguration以及ZuulProxyAutoConfiguration。Spring首先使用ZuulController來封裝ZuulServlet,而後定義一個ZuulHandlerMapping,使得除一些特殊請求之外(如/error)的大部分請求被轉發到ZuulController進行處理。源碼摘要以下:框架
@Configuration @EnableConfigurationProperties({ ZuulProperties.class }) @ConditionalOnClass(ZuulServlet.class) @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class) // Make sure to get the ServerProperties from the same place as a normal web app would @Import(ServerPropertiesAutoConfiguration.class) public class ZuulServerAutoConfiguration { ... @Bean public ZuulController zuulController() { return new ZuulController(); } @Bean public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) { ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController()); mapping.setErrorController(this.errorController); return mapping; } } public class ZuulController extends ServletWrappingController { public ZuulController() { setServletClass(ZuulServlet.class); setServletName("zuul"); setSupportedMethods((String[]) null); // Allow all } ... } public class ZuulHandlerMapping extends AbstractUrlHandlerMapping { ... private final ZuulController zuul; ... @Override protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception { if (this.errorController != null && urlPath.equals(this.errorController.getErrorPath())) { return null; } if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) return null; RequestContext ctx = RequestContext.getCurrentContext(); if (ctx.containsKey("forward.to")) { return null; } if (this.dirty) { synchronized (this) { if (this.dirty) { registerHandlers(); this.dirty = false; } } } return super.lookupHandler(urlPath, request); } ... private void registerHandlers() { Collection<Route> routes = this.routeLocator.getRoutes(); if (routes.isEmpty()) { this.logger.warn("No routes found from RouteLocator"); } else { for (Route route : routes) { registerHandler(route.getFullPath(), this.zuul); } } } }
SpringCloud默認定義了一些Filter來實現網關邏輯,其中最核心的Filter——RibbonRoutingFilter是負責實際轉發操做的,在它的過濾邏輯裏又集成了hystrix、ribbon等其餘重要框架。源碼摘要以下:運維
public class RibbonRoutingFilter extends ZuulFilter { @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); this.helper.addIgnoredHeaders(); try { RibbonCommandContext commandContext = buildCommandContext(context);//構建請求數據 ClientHttpResponse response = forward(commandContext);//執行請求 setResponse(response);//設置應答信息 return response; } catch (ZuulException ex) { throw new ZuulRuntimeException(ex); } catch (Exception ex) { throw new ZuulRuntimeException(ex); } } }
加載Filter的方式經過ZuulFilterInitializer擴展爲能夠從ApplicationContext中獲取。源碼摘要:
/** 代碼出自ZuulServerAutoConfiguration */ @Configuration protected static class ZuulFilterConfiguration { @Autowired private Map<String, ZuulFilter> filters;//從spring上下文中獲取Filter bean @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); } } public class ZuulFilterInitializer { private final Map<String, ZuulFilter> filters; ... @PostConstruct public void contextInitialized() { ... // 設置filter for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) { filterRegistry.put(entry.getKey(), entry.getValue()); } } }
隨着業務的不斷髮展,Zuul對於Netflix來講性能已經不太夠用,因而Netflix又開發了Zuul2。Zuul2最大的變革是基於Netty實現了框架的異步化,從而提高其性能。根據官方的數據,Zuul2的性能比Zuul1約有20%的提高。Zuul2架構圖以下:
因爲框架改成了異步的模式,Zuul2在提高性能的同時,也帶來了調試、運維的困難。在實際的使用當中,對於絕大多數公司來講,併發量遠遠沒有Netflix那樣龐大,選擇開發調試更簡單、且性可以用的Zuul1是更合適的選擇。
任金昊,隨行付架構部高級開發工程師。擅長分佈式、微服務架構,負責隨行付微服務生態平臺開發。