前言
本文起筆於2018-06-26周二,接了一個這周要完成的開發任務,需要先等其他人的接口,可能更新的會慢一些,還望大家見諒。這篇博客我們主要講Spring Cloud Zuul。項目地址:我的github
Spring Cloud Zuul大家可以理解為一個集網關(路由)、負載均衡、校驗過濾、結合服務治理框架、請求轉發時熔斷機制、服務聚合等 一系列功能。我們可以將Zuul當成一個門面,所有外部請求都經過Zuul的轉發到具體的服務實例,減少了每個服務之間互相鑒權代碼冗余問題,統一交給Zuul進行鑒權,在此基礎上集成上邊說的高級功能。路由功能相當於反向代理。
快速入門
1. 構建網關
項目結構:
創建一個新的Model,取名為ZuulGateway
,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.cnblogs.hellxz<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>ZuulGateway<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>1.0-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.cloud<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-cloud-starter-parent<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>Dalston.SR5<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">relativePath</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-comment"><!-- Spring Cloud Zuul的依賴 --></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.cloud<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-cloud-starter-zuul<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">build</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-maven-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-compiler-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"><<span class="hljs-name">source</span>></span>1.8<span class="hljs-tag"></<span class="hljs-name">source</span>></span>
<span class="hljs-tag"><<span class="hljs-name">target</span>></span>1.8<span class="hljs-tag"></<span class="hljs-name">target</span>></span>
<span class="hljs-tag"></<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">build</span>></span>
</project>
注意:
spring-cloud-starter-parent
的版本用的Dalston.SR5
,原因是我發現如果用1.5.9.RELEASE
並不會給給zuul的依賴引用進來,還需要額外指定版本號,Dalston.RELEASE
也是不能引入依賴的,如果想使用1.5.9.RELEASE
,使用spring-boot-starter-parent
指定1.5.9.RELEASE
版本,並在下邊的依賴中指定spring-cloud.version
為RELEASE
即可
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
創建主類ZuulApp
package com.cnblogs.hellxz;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy //開啟zuul網關服務功能
@SpringCloudApplication
public class ZuulApp {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
SpringApplication.run(ZuulApp.class, args);
}
}
2. 請求路由
a. 傳統路由實現方式
在resources
包下創建application.yml
spring:
application:
name: zuul-gateway
server:
port: 5555
zuul:
routes:
traditional-url: #傳統的路由配置,此名稱可以自定義
path: /tr-url/** #映射的url
url: http://localhost:9001/ #被映射的url
這里使用了傳統路由的方式先做一個展示,之后 會整合Eureka注冊中心實現
測試一下,分別啟動注冊中心、服務提供者、FeignCustomer、剛建好的zuul項目,我們直接訪問zuul項目,使用映射的url進行測試
如上圖,請求被轉發到了http://localhost:9001/feign/hello
接口
b. 面向服務的路由
使用傳統路由對運維人員很不友好,需要大量的時間去維護路由的映射關系,需要轉發的url少倒還好,多起來就會更容易配置出錯。
為了解決這個問題,Spring Cloud Zuul實現了與Spring Cloud Eureka的無縫結合,讓path不是映射到具體url上,而是映射到某個具體的服務上,具體的url交給Eureka的服務發現機制去自動維護,這類路由稱為面向服務的路由。
將Zuul注冊到中心的服務的同時,Zuul同時能夠獲取當前注冊中心中的服務清單,從清單中挑出實例進行請求轉發,從而達到請求轉發的完整路由機制。
改造我們之前的項目,為pom.xml添加Eureka的依賴,使zuul項目注冊到注冊中心中
<!-- Eureka的依賴 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
修改application.yml
,在zuul.routes
節點下加入配置,為了防止出錯,這里給出了包含上文中的傳統路由配置,這里要注意的是面向服務的路由需要指定注冊中心的地址
zuul:
routes:
traditional-url: #傳統的路由配置,此名稱可以自定義
path: /tr-url/** #映射的url
url: http://localhost:9001/ #被映射的url
orient-service-url: #面向服務的路由配置,此名稱可以自定義
path: /os-url/**
service-id: feign-customer #服務名
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/
重啟zuul項目,postman進行測試,結果相同,路由正常
這里為了方便都映射到了
http://localhost:9001/
也就是feign-customer
的項目,大家可以自行體驗
3. 請求過濾
微服務應用中的每個客戶端在提供服務的接口時,都會將訪問權限加以限制,並不會放開所有的接口,為了安全,我們應該為每個微服務加入校驗簽名和鑒權等的過濾器或者攔截器,這樣一來,會增加日后系統的維護難度,同時大部分的校驗和鑒權的邏輯代碼是相同的,那么我們就應該將這些重復邏輯提取出來,上文中曾說到“Zuul相當於整個微服務系統的門面”,那么接下來我們來看下在zuul網關中實現客戶端的請求校驗,即Spring Cloud Zuul 的核心功能之一的請求過濾
我們先定義一個簡單的Zuul過濾器,實現檢查HttpServletRequest中是否有accessToken參數,如果有就進行路由,沒有則拒絕訪問,還回 401 Unauthorized 錯誤
創建一個filter包下創建AccessFilter,繼承ZuulFilter並重寫以下方法
package com.cnblogs.hellxz.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
-
訪問過濾器
*/
public class AccessFilter extends ZuulFilter {private final Logger logger = LoggerFactory.getLogger(AccessFilter.class);
/**
- 過濾器類型選擇:
- pre 為路由前
- route 為路由過程中
- post 為路由過程后
- error 為出現錯誤的時候
- 同時也支持static ,返回靜態的響應,詳情見StaticResponseFilter的實現
- 以上類型在會創建或添加或運行在FilterProcessor.runFilters(type)
*/
public String filterType() {
return "pre"; //ZuulFilter源碼中注釋"pre"為在路由前過濾
}
/**
- 用來過濾器排序執行的
- @return 排序的序號
*/
public int filterOrder() {
return 0;
}
/**
- 是否通過這個過濾器,默認為true,改成false則不啟用
*/
public boolean shouldFilter() {
return false; //返回true表示執行這個過濾器
}
/**
- 過濾器的邏輯
*/
public Object run() {
//獲取當前請求上下文
RequestContext ctx = RequestContext.getCurrentContext();
//取出當前請求
HttpServletRequest request = ctx.getRequest();
logger.info("進入訪問過濾器,訪問的url:{},訪問的方法:{}",request.getRequestURL(),request.getMethod());
//從headers中取出key為accessToken值
String accessToken = request.getHeader("accessToken");//這里我把token寫進headers中了
//這里簡單校驗下如果headers中沒有這個accessToken或者該值為空的情況
//那么就攔截不入放行,返回401狀態碼
if(StringUtils.isEmpty(accessToken)) {
logger.info("當前請求沒有accessToken");
//使用Zuul來過濾這次請求
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
logger.info("請求通過過濾器");
return null;
}
}
在上個實驗的基礎上重啟這個Zuul項目,使用postman訪問http://localhost:5555/os-url/feign/hello
沒有在headers中加accessToken的測試
在headers中加accessToken的測試
注意:這里除了在類頭上打上@Component注解外,還可以使用@Bean注入的辦法
@Bean public AccessFilter accessFilter(){ return new AccessFilter(); }
快速入門這一塊就到這里,總結一下:
- 它作為系統的統一入口,屏蔽了系統內部各個微服務的細節
- 可以和服務治理框架結合,實現自動化服務實例維護和負載均衡
- 實現接口權限校驗與微服務業務邏輯的解耦
- 通過網關中的過濾器,在各生命周期中去校驗請求的內容,將原本在對外的服務層做的校驗前移,保證微服務的無狀態性,降低測試難度,解放程序員專注業務的處理
路由詳解
1.傳統路由配置
傳統路由就是不依賴於服務發現機制通過配置文件映射服務實例關系來實現的API網關對外請求路由。
沒有服務治理框架的幫助,不同服務實例的數量采用不同的方式配置來實現路由規則
1.1 單實例配置
通過zuul.routes.<路由名>.path
與zuul.routes.<路由名>.url
舉例:
zuul:
routes:
service-provider:
path: /eureka-service/**
url: http://localhost:8080
請求進來的時候先去匹配path,然后用這個path的路由名去找對應的url,進行請求轉發
1.2 多實例配置
通過zuul.routes.<路由名>.path
與zuul.routes.<路由名>.serviceId
舉例:
zuul:
routes:
service-provider:
path: /eureka-service/**
serviceId: eureka-service
ribbon:
eureka:
enabled: false
eureka-service:
ribbon:
listOfServers: http://localhost:8080/,http://localhost:8081/
請求進來的時候先去匹配path,然后用這個path的路由名去找對應的服務名,這里需要手動去命名服務名稱,配合ribbon.listOfServers
參數實現服務與實例的維護。由於存在多個實例,API網關在進行路由轉發時需實現負載均衡,於是使用Ribbon的配合,Zuul中自帶了對Ribbon的依賴。
- 參數說明
ribbon.eureka.enabled
:由於zuul.routes.<路由名>.serviceId
用來指定服務名稱,默認Ribbon會根據發現機制來獲取配置服務名對應的實例清單,但是,這里沒有使用Eureka之類的服務發現治理框架,所以需要將該參數設為false,否則配置的serviceId獲取不到對應實例的清單eureka-service.ribbon.listOfServers
:該參數內容與zuul.routes.<路由名>
的配置相對應,此配置打頭的eureka-service
對應了serviceId的值,相當於手動維護了在該應用內部手工維護了服務與實例的對應關系
無論單實例還是多實例的配置方式,傳統路由方式都需要手動為每一對映射指定一個名稱,每個<路由名>對應了一條路由規則;每第路由規則都必須有一個paht用來匹配請求路徑表達式,並通過與之相對應的url或serviceId屬性來請求表達式映射的url或服務名
2. 服務路由配置
快速入門中已經講了面向服務路由的配置,通過與Eureka的整合,實現了對服務實例的自動維護,所以在使用服務路由的時候,無須指定serviceId所指定具體服務實例地址,只需要通過zuul.routes.<路由名>.path
與zuul.routes.<路由名>.serviceId
成對配置即可。
舉例:
zuul:
routes:
eureka-service:
path: /eureka-service/**
serviceId: eureka-service
面向服務的路由配置除了上邊的方法還有一種更簡單的方式:zuul.routes.<服務名>=<映射地址>
舉例:
zuul:
routes:
eureka-service: /eureka-service/**
需要注意的是,這里邊本人舉的例子用的路由名一直是和serviceId保持相同,這里是可以不同的,請不要在這里迷惑
與傳統路由相比,有外部請求到API網關的時候,面向服務路由發生了什么?
當有外部請求到達API網關的時候,根據請求的URL路徑去匹配path的規則,通過path找到路由名,去找對應的serviceId的服務名,
- 傳統路由就會去根據這個服務名去找listOfServers參數,從而進行負載均衡和請求轉發
- 面向服務路由會從注冊到服務治理框架中取出服務實例清單,通過清單直接找到對應的實例地址清單,從而通過Ribbon進行負載均衡選取實例進行路由(請求轉發)
3. 服務路由的默認配置
Eureka與Zuul整合為我們省去了大量的維護服務實例清單的配置工作,但是實際操作中我們會將path與serviceId都用服務名開頭,如上邊我所舉的幾個例子都是,這樣的配置其實Zuul已經默認為我們實現了,當我們直接使用http://localhost:5555/feign-customer/hello
的時候,會發現我們沒有配置這個path確實訪問成功了!
其實,Zuul在注冊到Eureka服務中心之后,它會為Eureka中的每個服務都創建一個默認的路由規則,默認規則的path會使用serviceId配置的服務名作為請求前綴,如上圖的發生的情況。
這會使一些我們不希望的開放的服務有可能被外部訪問到,此時,我們可以使用zuul.ignored-services
參數來設置一個不自動創建該服務的默認路由。Zuul在自動創建服務路由的時候會根據這個表達式進行判斷,如果服務名匹配表達式,那么Zuul將跳過此服務,不為其創建默認路由。如下
zuul:
ignored-services: feign-customer,eureka-service
postman測試通過,結果和eureka-serivce一樣,此處不再贅述
如果不想使用自動創建默認路由功能,可以使用如下方法跳過默認路由
zuul:
ignored-services: '*'
4. 自定義路由映射規則
可以使用regexmapper在serviceId和路由之間提供約定。它使用正則表達式命名組從serviceId提取變量並將它們注入路由模式。
引用自官方文檔
ApplicationConfiguration.java.
@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
This means that a serviceId "myusers-v1" will be mapped to route "/v1/myusers/". Any regular expression is accepted but all named groups must be present in both servicePattern and routePattern. If servicePattern does not match a serviceId, the default behavior is used. In the example above, a serviceId "myusers" will be mapped to route "/myusers/" (no version detected) This feature is disabled by default and only applies to discovered services.
PatternServiceRouteMapper對象可以讓開發者通過正則表達式來自定義服務與路由映射的生成關系。其中構造函數的第一個參數用來匹配服務名稱是否符合該自定義規則的正則表達式(serviceId),而第二個參數則是定義服務名中定義的內容轉換出的路徑表達式(path),只要有符合第一個參數定義的serviceId,那么就會優先使用該實現構建出path,如果沒有找到則使用默認路由映射需——采用完整服務名做為前綴的路徑表達式。
5. 路徑匹配
無論傳統還是服務路由都是要使用path的,即用來匹配請求url中的路徑。
5.1 正則路徑匹配
path通常需要使用通配符,這里簡單講講
通配符 | 說明 | 舉例 |
---|---|---|
? | 匹配單個字符 | /feign/? |
* | 匹配任意數量字符,但不支持多級目錄 | /feign/* |
** | 匹配任意數量字符,支持多級目錄 | /feign/** |
如果有一個可以同時滿足多個path的匹配的情況,此時匹配結果取決於路由規則的定義順序,
這里需要注意的是:properties無法保證路由規則的順序,推薦使用yml格式配置文件
5.2 忽略表達式
Zuul提供了用於忽略路徑表達式的參數zuul.ignored-patterns
。使用該參數可以用來設置不希望被API網關進行路由的URL表達式。
舉例:
上文中使用過/feign/hello的接口,這次我們使用如下代碼去忽略這個/hello的接口
zuul:
ignored-patterns: /**/hello/**
使用postman測試並加入accessToken
服務終端打印輸出
測試發現明明存在的接口已經被忽略了
6. 路由前綴
為了方便全局為路由path增加前綴信息,Zuul提供了zuul.prefix
參數來進行設置,但是代理前綴會從默認路徑中移除掉,為避免這種情況,可以使用zuul.stripPrefix=false
來關閉移除代理前綴的動作,也可以通過zuul.routes.<路由名>.strip-prefix=false
來指定服務關閉移除代理前綴的動作
舉例:
zuul:
prefix: /api
routes:
feign-customer:
path: /feign/**
stripPrefix: false
書中提到使用路由前需謹慎,不同的版本可能會存在一些bug.
Cookie與頭信息
默認情況下,Zuul在請求路由時會過濾掉HTTP請求頭信息中的一些敏感信息,防止這些敏感的頭信息傳遞到下游外部服務器。但是如果我們使用安全框架如Spring Security、Apache Shiro等,需要使用Cookie做登錄和鑒權,這時可以通過zuul.sensitiveHeaders
參數定義,包括Cookie、Set-Cookie、Authorization三個屬性來使Cookie可以被傳遞。
可以分為全局指定放行Cookie和Headers信息和指定路由放行
1.全局放行
zuul:
sensitiveHeaders: Cookie,Set-Cookie,Authorization
2.指定路由名放行
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
如果全局配置和路由配置均有不同程度的放行,那么采取就近原則,路由配置的放行規則將生效
本地跳轉
遷移現有應用程序或API時的一種常見模式是“關閉”舊的端點,並慢慢地用不同的實現替換它們。Zuul代理是一個有用的工具,因為可以使用它來處理來自舊端點客戶端的所有流量,但會將某些請求重定向到新端點。
zuul.routes.<路由名>.url=forward:/<要跳轉到的端點>
舉例:
zuul:
routes:
first:
path: /first/**
url: http://first.example.com
second:
path: /second/**
url: forward:/first
這樣就會將請求到/second端點的請求轉發到/first端點
Hystrix和Ribbon支持
上文有講過,Zuul中包含了Hystrix和Ribbon的依賴,所以Zuul擁有線程隔離和斷路器的自我保護功能,以及對服務調用的客戶端負載均衡,需要注意的是傳統路由也就是使用path與url映射關系來配置路由規則的時候,對於路由轉發的請求不會使用HystrixCommand來包裝,所以沒有線程隔離和斷路器的保護,並且也不會有負載均衡的能力。所以我們在使用Zuul的時候推薦使用path與serviceId的組合來進行配置。
1. 設置Hystrix超時時間
使用hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
來設置API網關中路由轉發請求的命令執行時間超過配置值后,Hystrix會將該執行命令標記為TIMEOUT並拋出異常,Zuul會對該異常進行處理並返回如下JSON信息給外部調用方
{
"timestamp":20180705141032,
"status":500,
"error":"Internal Server Error",
"exception":"com.netflix.zuul.exception.ZuulException",
"message":"TIMEOUT"
}
2. 設置Ribbon連接超時時間
使用ribbon.ConnectTimeout
參數創建請求連接的超時時間,當ribbon.ConnectTimeout的配置值小於hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值時,若出現請求超時的時候,會自動進行重試路由請求,如果依然失敗,Zuul會返回如下JSON信息給外部調用方
{
"timestamp":20180705141032,
"status":500,
"error":"Internal Server Error",
"exception":"com.netflix.zuul.exception.ZuulException",
"message":"NUMBEROF_RETRIES_NEXTSERVER_EXCEEDED"
}
如果ribbon.ConnectTimeout的配置值大於hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值時,當出現請求超時的時候不會進行重試,直接超時處理返回TIMEOUT的錯誤信息
3. 設置Ribbon的請求轉發超時時間
使用ribbon.ReadTimeout
來設置請求轉發超時時間,處理與ribbon.ConnectTimeout類似,不同點在於這是連接建立之后的處理時間。該值小於hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds的配置值時報TIMEOUT錯誤,反之報TIMEOUT的錯誤。小於的時候會先重試,不成才報錯;大於的時候直接報錯。
4. 關閉重試配置
- 全局配置
zuul.retryable=false
- 針對路由配置
zuul.routes.<路由名>.retryable=false
過濾器解釋
1. 過濾器
在快速入門的請求過濾這一節的學習中,我們使用的pre類型的過濾器,那時我們就比較好奇:為什么不用route或其它類型?
實際上,路由運行過程中,它的路由映射和請求轉發是由幾個不同的過濾器完成的。其中,
- pre類型的過濾器用於路由的映射,將請求路徑和路由規則進行匹配,用以找到需要轉發目標地址。
- route類型的過濾器用來請求轉發,從pre類型過濾器獲取的目標地址進行轉發。
在Spring Cloud Zuul 中實現過濾器必須包含4 個基本特征:過濾類型、執行順序、執行條件、具體操作。實際上就是ZuulFilter抽象類中定義的抽象方法:
String filterType();
int filterOrder();
boolean shouldFilter();
Object run();
這里分別進行解釋:
- filterType:該方法需要返回一個字符串來代表過濾器的類型,而這個類型就是Zuul中的4種不同生命周期的過濾器類型,如下
- pre:在請求到達路由前被調用
- route:在路由請求時被調用
- error: 處理請求時發生的錯誤時被調用。
- post:在route和error過濾器之后被調用,最后調用。
- filterOrder:通過int值定義過濾器執行順序,數值越小優先級越高。
- shouldFilter:返回布爾值來判斷該過濾器是否執行。
- run:過濾器的具體邏輯。可以在此確定是否攔截當前請求等。
2. 請求的生命周期
和書的作者一樣,我也去爬了下Zuul的官方wiki,這里我們簡化梳理一下流程
首先HTTP請求到達Zuul,最先來到pre過濾器,在這里會去映射url patern到目標地址上,然后將請求與找到的地址交給route類型的過濾器進行求轉發,請求服務實例獲取響應,通過post類型過濾器對處理結果進行加工與轉換等操作返回。error類型的過濾器比較特殊,在這整個請求過程中只要有異常才會觸發,將異常結果交給post類型過濾器加工返回
routing在filterType方法返回的類型是"route",不是routing,查官方代碼發現的
3. 禁用過濾器
說到禁用過濾器,第一想到的是自定義的過濾器中shouldFilter返回false,實際應用中,這樣還需要重新編譯代碼。
Zuul貼心地提供了一個參數用來禁用指定過濾器,zuul.<過濾器名>.<過濾器類型>.disable=true
之前我們的實驗必須headers中有accessToken才能通過AccessFilter,現在我們禁用一下試試
zuul:
AccessFilter:
pre:
disable: true #禁用名為AccessFilter的過濾器
測試結果:
終端也沒有輸出進入過濾器的字樣,禁用成功。
書中這部分還有很多解讀源碼的部分,這里就不上了,有興趣去買書看吧
動態加載
在微服務中,API網關擔負着外部統一入口的重任,與其他服務不同,它必須保證不關閉不停機,從而確保整個系統的對外服務。所以我們就不能關機改東西再上線,這樣會影響到其它服務。Zuul早已考慮了這點,它實現了不關機狀態下的動態路由和動態添加/刪除過濾器等功能。接下來我們分別來看看這些功能如何使用。
1. 動態路由
之前我們對於路由的規則控制幾乎都是在application.yml或application.properties中完成的,網關服務不同於其它服務,需要不關閉不停機,所以這需要和Spring Cloud Config 組合起來,起到動態刷新配置路由的功能,這里簡單說一下Config的工作原理,下一文會詳細講
Spring Cloud Config 的Server端無需注冊到注冊中心,只需要配置好github的路徑以及在github上配好配置文件,用這個Server去拉取下來。然后我們的動態路由項目通過添加Config 的Client的依賴,這樣就可以起到動態路由的功能
這里因為書中也是把這塊放在了Spring Cloud Config前講的,看了下官方文檔,發現他最先講的就是Spring Cloud Config……這里留到下文講了
項目demo的結構如下:
創建一個新項目取名為DynamicRouteZuul
,pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.cnblogs.hellxz<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>DynamicRouteZuul<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>1.0-SNAPSHOT<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.cloud<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-cloud-starter-parent<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">version</span>></span>Dalston.SR5<span class="hljs-tag"></<span class="hljs-name">version</span>></span>
<span class="hljs-tag"><<span class="hljs-name">relativePath</span>/></span>
<span class="hljs-tag"></<span class="hljs-name">parent</span>></span>
<span class="hljs-tag"><<span class="hljs-name">dependencies</span>></span>
<span class="hljs-comment"><!-- Zuul的依賴 --></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.cloud<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-cloud-starter-zuul<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-comment"><!-- Eureka的依賴 --></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.cloud<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-cloud-starter-eureka<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-comment"><!--Config客戶端--></span>
<span class="hljs-tag"><<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.cloud<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-cloud-starter-config<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependency</span>></span>
<span class="hljs-tag"></<span class="hljs-name">dependencies</span>></span>
<span class="hljs-tag"><<span class="hljs-name">build</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-maven-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.apache.maven.plugins<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>maven-compiler-plugin<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span>
<span class="hljs-tag"><<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"><<span class="hljs-name">source</span>></span>1.8<span class="hljs-tag"></<span class="hljs-name">source</span>></span>
<span class="hljs-tag"><<span class="hljs-name">target</span>></span>1.8<span class="hljs-tag"></<span class="hljs-name">target</span>></span>
<span class="hljs-tag"></<span class="hljs-name">configuration</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugin</span>></span>
<span class="hljs-tag"></<span class="hljs-name">plugins</span>></span>
<span class="hljs-tag"></<span class="hljs-name">build</span>></span>
</project>
這個文件我就不多講了,就是maven的那一套,然后我們加
我們在java目錄下創建一個主類DynamicZuulApp,
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@EnableZuulProxy
@SpringCloudApplication
@ComponentScan("com.cnblogs.hellxz")
public class DynamicZuulApp {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
SpringApplication.run(DynamicZuulApp.class, args);
}
<span class="hljs-meta">@Bean</span>
<span class="hljs-meta">@RefreshScope</span>
<span class="hljs-meta">@ConfigurationProperties</span>(<span class="hljs-string">"zuul"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> ZuulProperties <span class="hljs-title">zuulProperties</span><span class="hljs-params">()</span></span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ZuulProperties();
}
}
同時創建一個com.cnblogs.hellxz的包,這里有這個包是因為如果你沒有包用來包掃描,會報一個錯誤,如下圖。
這里為了提github代碼的時候不會將這個空目錄忽略掉,加了一個只有包路徑的一個類
接下來,我們在resources下我們創建一個bootstrap.yml
spring:
application:
name: dynamic-route-zuul #這個名字用做拉取配置文件的名稱
cloud:
config:
uri: http://localhost:7001/ #指定Spring Cloud Config開放的項目端口
server:
port: 5556
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/ #注冊到注冊中心中
其中,spring.application.name不僅作為注冊中心的名稱,Config Client會使用這個名稱去Config Server項目中拉取dynamic-route-zuul.properties或yml
測試
分別啟動注冊中心、服務提供者、ConfigServer還有剛剛創建的動態路由項目
啟動動態路由項目的過程中會看到控制台打印了如下圖
博客園的圖片無法看大圖,想看大圖可以復制這個圖片的地址放到地址欄訪問查看
我們在github中配置了dynamic-route-zuul.yml為
zuul:
routes:
service-provider:
path: /service/**
serviceId: eureka-service
也就是說,我們為service路徑匹配到了服務提供者項目中,我們在這里使用postman訪問一下
至此測試成功
這里省去了動態過濾器的實現,因為用到groovy,本人能力有限,有興趣了解這一塊的請看書查驗
后記
這篇文章處在一個很尷尬的時間里,正如開頭說的是開始於6.26日,一個是最近工作忙,一個是有些代碼沒有測試成功就沒有提github上,然后回去只能干看着,重新寫還浪費時間……然后今天抽空毅然決然的把這篇文章發了出來,看到之前幾篇文章最近的閱讀數不少,我心甚慰,如果你有更好的想法和建議,歡迎大家評論拍磚。
最后感謝一下《Spring Cloud微服務實戰》的作者 程序員DD 翟永超 老師,書寫的不錯,Spring Cloud坑本來就不少,這種情況下還能將這些概念說的比較清晰,實屬不易。
下文我們開始Spring Cloud Config的學習
本文參考:
《Spring Cloud微服務實戰》的作者 程序員DD 翟永超
原文地址:https://blog.csdn.net/lifupingcn/article/details/88062637