首先有必要了解一下什麼是Zuul,它和Spring Cloud有什麼關係。
Zuul在Spring Cloud中承擔著閘道器的職責,可以理解為客戶端和服務端互動中的唯一通道。所有的客戶端請求都會首先發送到閘道器,而後路由轉發到對應的微服務。同時,Zuul還提供了諸如服務聚合、許可權校驗等,另外也對客戶端與服務端的互動起到了解耦的作用。
我們可以通過一個具體的場景來理解一下。假設需要開發一個校園圖書管理系統,在客戶端可能發起登入、檢視圖書列表、進行訂閱等請求,如下圖所示:
而其中,即使是非本校的學生也可以在客戶端查詢有哪些圖書,但是隻有登入成功的本校學生才能發起借閱。這時客戶端需要做的事情就多了,既要區分不同的請求發往哪個服務,還要判斷當前的許可權是否可以呼叫這個服務。另外,在客戶端進行許可權的控制也存在安全隱患,前後端的職責劃分也不夠明確。在引入Zuul閘道器之後的效果是這樣:
服務的路由以及許可權的校驗統統在閘道器層面完成即可。這樣還有一個好處就是後續即使把圖書查詢服務進行拆分,變成中文圖書查詢與英文圖書查詢,或者進行其他的服務合併,對於客戶端也基本是無感知了。
瞭解了Zuul的功能之後,我們來探究一下Zuul的原理。
Zuul的核心是一組過濾器,而Zuul的絕大部分功能也都是通過這一組過濾器實現的。Zuul提供了對這些過濾器進行動態載入、編譯以及執行的框架。這一組過濾器包括pre、routing、post、error等,通過一張圖片來了解一下這些過濾器分別在一筆請求的哪些環節生效:
從上圖中可以看到對應的幾類過濾器分別是如下的作用:
PRE:這種過濾器在請求被路由之前呼叫。我們可利用這種過濾器實現身份認證、在叢集中選擇具體需要請求的微服務、記錄日誌資訊等。
ROUTING:這種過濾器用於構建傳送給微服務的請求,並使用Apache HttpClient或Netfilx Ribbon請求微服務(題外話:這裡也能看出,Ribbon是在客戶端實現負載均衡,這一點和Nginx不同)。
POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應新增標準的HTTP Header、收集統計資訊和指標、將響應從微服務傳送給客戶端等。
ERROR:在其他階段發生錯誤時執行該過濾器。
Spring Cloud藉助Eureka進行服務註冊與發現,微服務的訪問通過REST請求。Zuul的訪問方式也是REST,其URL地址預設格式為:
http://zuulHostIp:port/要訪問的服務名稱/服務中的URL
我這裡為了方便在一個測試的微服務專案中為Zuul閘道器新增一個Module(在不在一個專案無所謂,大家通過同一個Eureka服務進行註冊發現即可):
pom中需要新增Zuul的相關依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.7.RELEASE</version>
</dependency>
application.yml中指定閘道器服務名、連線的Eureka服務資訊等:
server:
port: 10290
spring:
application:
name: spring-cloud-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9090/eureka
fetch-registry: true
register-with-eureka: true
接下來我們新增路由配置來驗證Zuul的路由轉發功能:
zuul:
routes:
spring-cloud-service-provider:
path: /provider/**
url: http://127.0.0.1:10090
spring-cloud-service-provider為路由規則的條目名稱。path為向Zuul閘道器進行請求時緊跟在埠後的服務名稱,**表示具體服務下的url。而url項則需要填寫目的服務的真實地址埠資訊。
建立應用時需要新增自動發現以及Zuul的註解:
@SpringCloudApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class ZuulGatewayApplication { public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
啟動Eureka服務、目的服務以及Zuul閘道器服務驗證一下,沒問題:
接下來我們玩一下Zuul的核心,過濾器。可以試著建立一個PRE過濾器,來列印請求的詳細資訊。
可以看到在繼承ZuulFilter類時需要實現如下四個方法:
filterType:表示過濾器型別,值域分別對應不同的生命週期也就是pre、error、post以及route。
filterOrder:同種類(生命週期)過濾器的執行優先順序,按照返回值升序進行優先順序的確定。
shouldFilter:表示當前過濾器是否生效。
run:過濾器的具體執行邏輯。
補充之後的過濾器程式碼如下所示:
@Component
public class PreLogFilter extends ZuulFilter { public String filterType() {
return "pre";
} public int filterOrder() {
return 0;
} public boolean shouldFilter() {
return true;
} public Object run() throws ZuulException { RequestContext rc = RequestContext.getCurrentContext();
HttpServletRequest request = rc.getRequest(); System.out.println("PreLogFilter enters with [url=" + request.getRequestURL() + "]...");
return null;
}
}
重新啟動Zuul閘道器服務之後再次進行請求,可以看到控制檯列印如下輸出:
參考資料:
https://www.cnblogs.com/jing99/p/11696192.html
https://zhuanlan.zhihu.com/p/138943446
https://blog.csdn.net/luckystar_99/article/details/105114198