API 網關是一個更爲智能的應用服務器,它的定義相似於面向對象設計模式中的 Facade 模式,它的存在就像是整個微服務架構系統的門面同樣,全部的外部客戶端訪問都須要通過它來進行調度和過濾。它除了要實現請求路由、負載均衡、校驗過濾等功能以外,還須要更多能力,好比與服務治理框架的結合、請求轉發時的熔斷機制、服務的聚合等一系列高級功能。java
在 Spring Cloud 中了提供了基於 Netflix Zuul 實現的 API 網關組件 Spring Cloud Zuul。git
SpringBoot 版本號:2.1.6.RELEASE
SpringCloud 版本號:Greenwich.RELEASEgithub
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies>
server: port: 5555 spring: application: name: cloud-zuul eureka: client: service-url: defaultZone: http://user:password@localhost:1111/eureka/
// 開啓 Zuul 的Api 網關服務功能 @EnableZuulProxy @EnableDiscoveryClient @SpringBootApplication public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
Spring Cloud Zuul 經過與 Spring Cloud Eureka 進行整合,將自身註冊爲 Eureka 服務治理下的應用,同時從 Eureka 中得到了全部其餘微服務的實例信息。這樣的設計很是巧妙地將服務治理體系中維護的實例信息利用起來,使得將維護服務實例的工做交給了服務治理框架自動完成,再也不須要人工介入。而對於路由規則的維護, Zuul 默認會將經過以服務名做爲
ContextPath 的方式來建立路由映射。好比上面的配置,Spring Cloud Zuul 會爲 Eureka 中的每一個服務都自動建立一個默認路由規則,默認規則的 path 會使用 serviceId 配置的服務名做爲請求前綴 —— 對於 /'serviceId'/** 的請求,會被轉發到 serviceId 的服務處理。spring
能夠設置不對每一個服務自動建立路由規則嗎?設計模式
zuul: # Zuul 將對全部的服務都不自動建立路由規則 ignored-services: "*"
若是咱們手動配置路由是怎樣的呢?推薦下面的方式:服務器
zuul: routes: client-2: path: /client-2/** serviceId: cloud-eureka-client # zuul.routes.<serviceid> = <path> cloud-eureka-client: /client-3/** client-4: path: /client-4/** # 請求轉發 —— 僅限轉發到本地接口 url: forward:/local
其中, ?:匹配任意單個數量字符;*:匹配任意多個數量字符;**:匹配任意多個數量字符,支持多級目錄。架構
不推薦使用 url 的方式來配置路由,該請求是直接經過 httpClient 包實現的, 而沒有使用 Hystrix 命令進行包裝, 因此這類請求並無線程隔離和斷路器的保護。app
若是咱們要過濾掉某些 url,讓它不走路由規則呢?負載均衡
zuul: # 對某些 url 設置不通過路由選擇 ignored-patterns: {"/**/world/**","/**/zuul/**"}
Spring Cloud Zuul 對 "/zuul" 的路徑訪問的會繞過 dispatcherServlet, 被 ZuulServlet 處理,主要用來應對處理大文件上傳的狀況。框架
zuul: servlet-path: /zuul
Spring Cloud Zuul 提供了一套過濾器機制,開發者能夠經過使用 Zuul 來建立各類校驗過濾器,而後指定哪些規則的請求須要執行校驗邏輯,只有經過校驗的纔會被路由到具體的微服務接口,否則就返回錯誤提示。
要在 Zuul 實現過濾器機制也很簡單,只須要繼承 ZuulFilter 類便可。接下來,咱們來寫一個過濾器 TokenFilter,校驗接口參數中是否有 token 參數。
@Component public class TokenFilter extends ZuulFilter { private Logger logger = LoggerFactory.getLogger(TokenFilter.class); /** * 過濾器的類型,它決定過濾器在請求的哪一個生命週期中執行。這裏定義爲 pre, 表明會在請求被路由以前執行。路由類型有下面幾種: * <p> * - pre: 能夠在請求被路由以前調用。 * - routing: 在路由請求時被調用。 * - post: 在 routing 和 error 過濾器以後被調用。 * - error: 處理請求時發生錯誤時被調用。 * * @return */ @Override public String filterType() { return FilterConstants.PRE_TYPE; } /** * 過濾器的執行順序。當請求在一個階段中存在多個過濾器時,須要根據該方法返回的值來依次執行,數值越小,優先級越高。 * * @return */ @Override public int filterOrder() { return 0; } /** * 判斷該過濾器是否須要被執行。這裏咱們直接返回了true, 所以該過濾器對全部請求都會生效。實際運用中咱們能夠利用該函數來指定過濾器的有效範圍。 * * @return */ @Override public boolean shouldFilter() { return true; } /** * 過濾器的具體執行邏輯 * * @return * @throws ZuulException */ @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); logger.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString()); String token = request.getParameter("token"); if (StringUtils.isEmpty(token)) { logger.warn("token is empty"); // 令 zuul 過濾該請求,不對其進行路由 ctx.setSendZuulResponse(false); // 設置返回的錯誤碼 ctx.setResponseStatusCode(401); // 設置返回的 body ctx.setResponseBody(""); return null; } logger.info("access token is ok"); return null; } }
實際上,上面提到的 Zuul 路由功能在真正運行時,它的路由映射和請求轉發都是由幾個不一樣的過濾器完成的。因此,過濾器能夠說是 Zuul 實現 API 網關功能最爲核心的部件,每個進入 Zuul 的 HTTP 請求都會通過一系列的過濾器處理鏈獲得請求響應並返回給客戶端。下圖源自 Zuul 的官方Wiki 中關於請求生命週期的圖解, 它描述了一個 HTTP 請求到達 API 網關以後, 如何在各類不一樣類型的過濾器之間轉的詳細過程。
當外部 HTTP 請求到達 API 網關服務的時候,首先它會進入第一個階段 pre, 在這裏它會被 pre 類型的過濾器進行處理, 該類型過濾器的主要目的是在進行請求路由以前作一些前置加工,好比請求的校驗、限流等。在完成了 pre 類型的過濾器處理以後,請求進入第二個階段 routing, 也就是以前說的路由請求轉發階段,請求將會被 routing 類型過濾器處理。這裏的具體處理內容就是將外部請求轉發到具體服務實例上去的過程,當服務實例將請求結果都返回以後,routing 階段完成, 請求進入第三個階段 post。此時請求將會被 post 類型的過濾器處理,這些過濾器在處理的時候不只能夠獲取到請求信息,還能獲取到服務實例的返回信息,因此在 post 類型的過濾器中,咱們能夠對處理結果進行一些加工或轉換等內容。另外,還有一個特殊的階段 error, 該階段只有在上述三個階段中發生異常的時候纔會觸發,可是它的最後流向仍是 post 類型的過濾器,由於它須要經過 post 過濾器將最終結果返回給請求客戶端。
Zuul 中默認實現的 Filter:
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標記處理 Servlet 的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝 HttpServletRequest 請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記調試標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId |
route | 100 | SimpleHostRoutingFilter | url 請求轉發 |
route | 500 | SendForwardFilter | forward 請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
咱們能夠在配置文件中,選擇是否禁用某個過濾器。
zuul: # 禁用某個過濾器 zuul.<SimpleClassName>.<filterTye>.disable=true TokenFilter: pre: disable: true
經常 request 中有些 header 信息咱們不但願滲透到服務中去,好比 accessToken、sign、Cookie 等。或者咱們要保持 request 的 host 信息一致,該怎麼配置呢?
zuul: routes: client-2: path: /client-2/** serviceId: cloud-eureka-client # 敏感頭信息設置爲空,表示不過濾敏感頭信息,容許敏感頭信息滲透到下游服務器(針對單個服務的敏感頭部信息配置,下面兩個配置項選其一便可) sensitiveHeaders: "" customSensitiveHeaders: true # Spring Cloud Zuul在請求路由時,會過濾掉 HTTP 請求頭(Cookie、Set-Cookie、Authorization)信息中的一些敏感信息, sensitive-headers: {"Cookie", "Set-Cookie", "Authorization"} # 網關在進行路由轉發時爲請求設置 Host 頭信息(保持在路由轉發過程當中 host 頭信息不變) add-host-header: true # 請求轉發時加上 X-Forwarded-*頭域 add-proxy-headers: true
# 該參數能夠用來設置 API 網關中路由轉發請求的 HystrixCommand 執行超時時間,單位爲毫秒。 hystrix: command: default: execution: isolation: thread: timeoutinMilliseconds: 5000 ribbon: # 該參數用來設置路由轉發請求的時候,建立請求鏈接的超時時間。 ConnectTimeout: 500 # 該參數用來設置路由轉發請求的超時時間。 ReadTimeout: 2000 # 最大自動重試次數 MaxAutoRetries: 1 # 最大自動重試下一個服務的次數 MaxAutoRetriesNextServer: 1
其中,Hystrix 的配置參數能夠在 HystrixCommandProperties.java 中找到。
其中,Ribbon 的配置參數能夠在 CommonClientConfigKey.java 中找到。
另外須要注意的是,請求重試還須要將 zuul.retryable 設置爲 true。