【讀書筆記】7.API服務閘道器Spring Cloud Zuul
阿新 • • 發佈:2018-12-13
介紹
背景:
- 系統規模增大時,需要一套機制來降低維護路由規則與服務例項列表的難度
- 微服務架構中,解決微服務介面訪問時各種前置檢驗的冗餘問題
為了解決上述問題,API閘道器應運而生。Spring Cloud Zuul首先整合eureka,並註冊為eureka的一個應用,同時從eureka獲取其他應用的例項資訊。此外,Zuul本身還有一套過濾機制。
快速入門
本節搭建示例在上一節(feign)已提供壓縮包,下載地址===>演示專案下載
1. 搭建一個SpringBoot工程,命名api-gateway
需要引入zuul和eureka-client依賴
<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>
並且在啟動類貼上@EnableZuulProxy和@EnableEurekaClient 最後,配置檔案新增應用名和埠
spring.application.name=api-gateway server.port=5555 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka
2. 配置請求路由
2.1 傳統方式(path-url)
# 傳統路由1:單例項 zuul.routes.<路由名>.path=/xx與zuul.routes.<路由名>.url=http://xx繫結
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:5555/
# url方式還支援本地跳轉(forward)
zuul.routes.api-b-url.path=/api-b-url/**
zuul.routes.api-b-url.url=forward:/local
驗證方式:
- 訪問http://localhost:5555/api-a-url/index會跳轉到http://localhost:5555/index
- 訪問http://localhost:5555/api-b-url/hello會跳轉到http://localhost:5555/local/hello
# 傳統路由2:多例項 zuul.routes.<路由名>.path與zuul.routes.<路由名>.serviceId繫結(需要向註冊中心註冊)
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-service
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=feign-consumer
# 如果是多例項,需要手動維護例項清單
ribbon.eureka.enabled=false
hello-service.ribbon.listOfServers=http://localhost:8081
驗證方式
- 訪問http://localhost:5555/api-a/hello會跳轉到http://localhost:8081/hello(服務hello-service在8081埠)
- 訪問http://localhost:5555/api-b/hello會跳轉到http://localhost:9001/hello(服務feign-consumer在9001埠)
2.2 面向服務
# 服務路由 zuul.routes.<serviceId>=<path>
zuul.routes.hello-service=/api-a/**
zuul.routes.feign-consumer=/api-b/**
驗證
- 訪問http://localhost:5555/hello-service/api-a/hello會跳轉到http://localhost:8081/hello
- 訪問http://localhost:5555/api-b/feign-consumer會跳轉到http://localhost:9001/feign-consumer
2.3 其他配置
- 忽略表示式
zuul.ignored-patterns=/**/hello/**
- 示例:令含有/hello的介面不被訪問(但/hello1可以正常訪問)
- 現象:訪問http://localhost:5555/api-a/hello,404
- 路由字首(Finchley.SR1正常,已修復bug):zuul.prefix=/api-a
- Cookie與頭資訊,登入和鑑權問題(Cookie在SpringCloud Zuul預設不傳遞)
zuul.routes.<router>.custom-sensitive-headers=true
- 重定向問題,暴露了例項地址,需要設定host頭資訊:zuul.add-host-header=true
- Hystrix和Ribbon支援(需要path與serviceId繫結的方式)
- HystrixCommand執行的超時時間(要大於Ribbon的超時時間才能觸發重試)
- hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
- 請求連線的超時時間:ribbon.ConnectTimeout=3000
- 請求處理的超時時間:ribbon.ReadTimeout=60000
- 關閉重試機制:zuul.retryable=false
- HystrixCommand執行的超時時間(要大於Ribbon的超時時間才能觸發重試)
- 自定義對映規則
/** * 自定義路由規則: 微服務名,helloservice-v1 ===>/v1/helloservice/** * 相當於zuul.routes.helloservice-v1=/v1/helloservice/** * @return */ @Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
- 禁用過濾器
# 禁用指定型別的自定義攔截器,zuul.<SimpleClassName>.<filterType>.disable=true zuul.AccessFilter.pre.disable=true
3. 請求過濾
3.1 自定義過濾器示例
- 繼承ZuulFilter並實現其抽象方法
- 指定過濾器型別filterType:pre,route,post,error
- 指定過濾器執行順序filterOrder
- 判斷過濾器是否需要執行shouldFilter
- 在run()實現過濾器的具體邏輯
- 使用@component標記該過濾器為Spring元件(或者手動建立)
3.2 請求生命週期
- 過濾器型別filterType詳解
- pre,在請求被路由之前呼叫
- route,路由請求時呼叫
- post,routing和error之後呼叫,返回給客戶端
- error,處理請求發生錯誤時呼叫(上述三個階段)
核心過濾器原始碼位於spring-cloud-netflix-zuul依賴的org.springframework.cloud.netflix.zuul.filters包下
- 異常處理(Brixton.SR5版本,即SpringCloud微服務實戰(2017.5 第一版)使用的版本)
原理:SendErrorFilter的執行順序是0,是post階段第一個執行的過濾器,執行的邏輯是上下文是否包含"error.status_code",下面的兩種方法利用了此特性
- 方法1:在run()裡使用try-catch,一旦發生異常,在上下文中新增error.*引數,如下
public Object run() { try { int i = 1 / 0; } catch(Throwable throwable) { RequestContext ctx = RequestContext.getCurrentContext(); Throwable throwable = ctx.getThrowable(); ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); ctx.set("error.message", "自定義錯誤過濾器攔截到內部異常"); ctx.set("error.exception", throwable.getCause()); } return null; }
- 方法2:利用pre,route,post階段拋異常都會進入error階段的特性,自定義一個error過濾器統一處理
@Component public class ErrorFilter extends ZuulFilter { @Override public String filterType() { return "error"; } @Override public int filterOrder() { return 10; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { // 參考方法1 } }
@Component public class ErrorExtFilter extends SendErrorFilter{ @Override public String filterType() { return "error"; } @Override public int filterOrder() { //要大於上面ErrorFilter的順序(10) return 30; } @Override public boolean shouldFilter() { // 僅處理post丟擲的異常 RequestContext ctx = RequestContext.getCurrentContext(); ZuulFilter failedFilter = (ZuulFilter) ctx.get("failed.filter"); if (failedFilter != null && failedFilter.filterType().equals("post")) { return true; } return false; } @Override public Object run() throws ZuulException { // 參考方法1 }
- 方法1:在run()裡使用try-catch,一旦發生異常,在上下文中新增error.*引數,如下
- 異常處理(Finchley.SR1版本) 先來看看SendErrorFilter的原始碼 顯然,往上下文新增error.*的方式不可行了,那麼新的方式是怎樣的呢? 在run()一樣使用try-catch,但是捕獲到異常後,直接丟擲異常,後面有過濾器接收; 同時,post階段丟擲異常的異常,也自動處理了,無需再建立一個error過濾器。
由於尚未接觸分散式配置中心Config,本章暫不介紹動態路由和動態過濾器