首先有必要了解一下什么是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