Spring cloud(5)-路由閘道器(Zuul)
基於 Netflix
的開源框架 zuul
實現的 各個微服務之間都不存在單點,並且都註冊於 Eureka ,基於此進行服務的註冊於發現,再通過 Ribbon 進行服務呼叫,並具有客戶端負載功能。 問題點?
- 將我們具體的微服務地址加埠暴露出去?
- 如果系統龐大,服務拆分的足夠多那又有誰來維護這些路由關係呢?
- 服務呼叫之間的一些鑑權、簽名校驗怎麼做?
- 由於服務端地址較多,客戶端請求維護難度?
針對上述問題:SpringCloud 全家桶自然也有對應的解決方案: Zuul
。 Spring Cloud Zuul 將自身註冊為 Eureka 服務治理下的應用,從 Eureka 中獲取服務例項資訊,從而維護路由規則和服務例項。
API服務閘道器(API Gateway)服務
我們在所有的請求進來之前抽出一層閘道器應用,將服務提供的所有細節都進行了包裝,這樣所有的客戶端都是和閘道器進行互動 (統一入口)
,簡化了客戶端開發
- 將細粒度的服務組合起來提供一個粗粒度的服務
- 所有請求都匯入一個統一的入口,那麼整個服務只需要暴露一個api
- 對外遮蔽了服務端的實現細節,也減少了客戶端與伺服器的網路呼叫次數
-
客戶端負載
:Zuul 註冊於 Eureka 並集成了 Ribbon 所以自然也是可以從註冊中心獲取到服務列表進行客戶端負載。 -
動態路由
:解放運維。 -
監控與審查
-
身份認證與安全
-
壓力測試
: 逐漸增加某一個服務叢集的流量,以瞭解服務效能; -
金絲雀測試
-
服務遷移
-
負載剪裁
: 為每一個負載型別分配對應的容量,對超過限定值的請求棄用; -
靜態應答處理
新增依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> 複製程式碼
spring-cloud-starter-zuul本身已經集成了hystrix和ribbon
注意點 :
-
(傳統路由)
當使用path與url的對映關係來配置路由規則時,對於路由轉發的請求則不會採用HystrixCommand來包裝,所以這類路由請求就沒有執行緒隔離和斷路器保護功能,並且也不會有負載均衡的能力 -
(服務路由)
使用Zuul的時候儘量使用**path和serviceId
**的組合進行配置,這樣不僅可以保證API閘道器的健壯和穩定,也能用到Ribbon的客戶端負載均衡功能。
開啟服務註冊
@EnableZuulProxy @SpringBootApplication public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } } 複製程式碼
路由配置順序注意:
YAML(檔案格式,類似propeties) propeties
# 專案配置 spring.application.name=sbc-gateway-zuul server.port=8383 # 簡化路由配置[zuul.routes.微服務Id = 指定路徑] zuul.routes.user = /api/**(指定正則表示式來匹配路徑) # 忽略指定微服務 zuul.ignored-services=微服務Id1,微服務Id2... # 指定多個微服務負載 zuul.routes.user.path: /user/** zuul.routes.user.serviceId: user ribbon.eureka.enabled=false user.ribbon.listOfServers: 微服務1, 微服務2... # forward跳轉本地 zuul.routes.user.path=/user/** zuul.routes.user.url=forward:/user # 對指定路由開啟自定義敏感頭 zuul.routes.[route].customSensitiveHeaders=true zuul.routes.[route].sensitiveHeaders=[這裡設定要過濾的敏感頭] # 全域性 zuul.sensitiveHeaders=[這裡設定要過濾的敏感頭] # --Zuul的Http客戶端支援Apache Http、Ribbon的RestClient和OkHttpClient,預設使用Apache HTTP客戶端。-- # 啟用Ribbon的RestClient ribbon.restclient.enabled=true # 啟用OkHttpClient ribbon.okhttp.enabled=true # eureka地址 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ 複製程式碼
Zuul容錯與回退
- Zuul的Hystrix監控的
粒度
是微服務
,而不是某個API
- Zuul提供了一個
ZuulFallbackProvider
介面(最新版本建議直接繼承類FallbackProvider
,新版增加異常處理
),實現該介面就可以為Zuul實現回退功能
/** * @program: spring-cloud * @description: zuul容錯與回退 * @author: Mr.Tang * @create: 2018-06-20 14:25 **/ @Component public class ProductServiceFallbackProvider implements FallbackProvider { protected final Logger logger = LoggerFactory.getLogger(ProductServiceFallbackProvider.class); @Override public String getRoute() { // 注意: 這裡是route的名稱,不是服務的名稱,否則無法起到回退作用 return "ribbon-consumer"; } @Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); return headers; } @Override public InputStream getBody() throws IOException { logger.info("ribbon-consumer: 服務不可以"); return new ByteArrayInputStream("該服務暫不可用,請稍後重試!".getBytes()); } @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } }; } @Override public ClientHttpResponse fallbackResponse(Throwable cause) { if (cause != null && cause.getCause() != null) { String reason = cause.getCause().getMessage(); logger.info("Excption {}",reason); } return fallbackResponse(); } } 複製程式碼
路由重試(或者啟動備用服務來分散壓力)網路等原因導致服務不可用,需要進行重試,zuul結合 Spring Retry
新增Spring Retry依賴
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> 複製程式碼
開啟重試不使用重試,就必須考慮到是否能夠接受單個服務例項關閉和eureka重新整理服務列表之間帶來的短時間的熔斷(在未重新整理之前還是會訪問到熔斷中的服務降級方法)
#是否開啟重試功能 zuul.retryable=true #對當前服務的重試次數 ribbon.MaxAutoRetries=2 #切換相同Server的次數 ribbon.MaxAutoRetriesNextServer=0 複製程式碼
注意斷路器的其中一個作用就是防止故障或者壓力擴散。當壓力過大是,一個服務的宕機,路由將壓力轉向其它服務時有可能也會壓垮其它服務。斷路器的形式更像是提供一種友好的錯誤資訊,提高服務之間的容錯性。
Zuul過濾器
Zuul大部分功能都是通過過濾器來實現的。Zuul中定義了四種標準過濾器型別
-
PRE
: 路由之前。我們可利用這種過濾器實現身份驗證、在叢集中選擇請求的微服務、記錄除錯資訊等。 -
ROUTING
: 路由之時。這種過濾器用於構建傳送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務。 -
POST
: 路由之後。這種過濾器可用來為響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務傳送給客戶端等。 -
ERROR
: 在其他階段發生錯誤時執行該過濾器。
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還允許我們建立自定義的過濾器型別。 自定義(PRE)
如下: ZuulFilter
是Zuul中核心元件,通過繼承該抽象類,覆寫幾個關鍵方法達到自定義排程請求的作用
/** * @program: spring-cloud * @description: token驗證 * @author: Mr.Tang * @create: 2018-06-21 10:08 **/ public class TokenFilter extends ZuulFilter{ protected final Logger logger = LoggerFactory.getLogger(TokenFilter.class); @Override public String filterType() { //pre:路由之前 //routing:路由中 //post:路由後 //error:發生錯誤時 return "pre"; } @Override public int filterOrder() { // filter執行順序,通過數字指定 ,優先順序為0,數字越大,優先順序越低 return 0; } @Override public boolean shouldFilter() { // 是否執行該過濾器,此處為true,說明需要過濾 return true; } @Override public Object run() { this.logger.info("This is pre-type zuul filter."); RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String token = request.getParameter("token"); if(StringUtils.isNotBlank(token)){ requestContext.setSendZuulResponse(true); //對請求進行路由 requestContext.setResponseStatusCode(200); requestContext.set("isSuccess", true); return null; }else{ requestContext.setSendZuulResponse(false); //不對其進行路由 requestContext.setResponseStatusCode(400); requestContext.setResponseBody("token is empty"); requestContext.set("isSuccess", false); return null; } } } 複製程式碼
filterType() filterOrder() shouldFilter() run()
過濾器之間並不會直接進行通訊,而是通過 RequestContext
來共享資訊, RequestContext
是執行緒安全的。 開啟過濾器 在程式的啟動類 新增 Bean
@Bean public TokenFilter tokenFilter() { return new TokenFilter(); } 複製程式碼
禁用過濾器格式為: zuul.[filter-name].[filter-type].disable=true
如:
zuul.FormBodyWrapperFilter.pre.disable=true 複製程式碼
@EnableZuulServer和@EnableZuulProxy
-
@EnableZuulProxy
包含@EnableZuulServer
的功能,不會自動載入任何代理過濾器。 - 當我們需要執行一個沒有代理功能的Zuul服務,或者有選擇的開關部分代理功能時,那麼需要使用
@EnableZuulServer
替代@EnableZuulProxy
。 這時候我們可以新增任何ZuulFilter
型別實體類都會被自動載入,
Zuul 高可用
Zuul 現在既然作為了對外的第一入口,那肯定不能是單節點,對於 Zuul 的高可用有以下兩種方式實現。
-
Eureka 高可用
:部署多個 Zuul 節點,並且都註冊於 Eureka(有一個嚴重的缺點:那就是客戶端也得註冊到 Eureka 上才能對 Zuul 的呼叫做到負載,這顯然是不現實的。) -
基於 Nginx 高可用
:在呼叫 Zuul 之前使用 Nginx 之類的負載均衡工具進行負載,這樣 Zuul 既能註冊到 Eureka ,客戶端也能實現對 Zuul 的負載
結語
在 github
上有關於 Spring Cloud
完整的部署。
最後, ofollow,noindex">給個 star 吧 ~
個人部落格 ~
簡書 ~