SpringCloud-Zuul
什麼是 Zuul ?
zuul 是從裝置和網站到後端應用程式所有請求的前門,為內部服務提供可配置的對外 URL 到服務的對映關係. 基於 JVM 的後端路由器.其具備一下功能:
- 認證與鑑權
- 壓力控制
- 金絲雀測試
- 動態路由
- 負載削減
- 靜態響應處理
- 主動流量管理
其地層是基於 Servlet, 本質元件是一系列的 Filter 所構成的責任鏈.
示例
建立 Eureka Server 註冊中心, Eureka Client (服務提供者工程), Zuul Server 閘道器中心. 三個工程
Eureka Client 工程:
匯入依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
配置檔案:
server: port: 8081 spring: application: name: eureka-clientA eureka: client: service-url: defaultZone: http://localhost:8761/eureka/
主程式啟動類
@SpringBootApplication
@EnableDiscoveryClient
public class EurelaClientAApplication {
public static void main(String[] args) {
SpringApplication.run(EurelaClientAApplication.class, args);
}
}
測試介面:
@RestController public class TestController { @RequestMapping("/add") public Integer add(Integer a, Integer b) { return a + b; } }
Zuul Server 工程:
依賴:
<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>
配置檔案:
server:
port: 8080
spring:
application:
name: zuul-server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
routes:
clienta:
path: /client/** #凡是 /client 開頭的 HTTP 請求, 都會被對映到 clientA 這個服務例項上去, 假設 clientA 的服務例項的埠為 8081
# 那麼 http://localhost:8080/client/getuser 的請求,在內部會呼叫對映的地址 http://localhost:8081/getuser 進行服務
# 請求, 獲取到結果,然後返回
serviceId: eureka-clientA #服務例項 ID
主程式啟動類:
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
然後依次啟動 Eureka Server, Eureka Client, Zuul Server 三個工程, 然後先直接訪問以下介面: http://localhost:8081/add?a=10&b=20 可以看到輸出, 說明服務能正常訪問:
然後通過 zuul 去訪問服務: http://localhost:8080/client/add?a=10&b=20 可以看到以下輸出, 說明通過 zuul 能正確訪問服務:
當我們呼叫 http://localhost:8080/client/add?a=10&b=20 地址的時候, 實際上在內部呼叫的時候呼叫的是: http://localhost:8081/add?a=10&b=20 , 這是因為我們在 zuul 中配置了路由規則, 當向 zuul 發起請求的時候, 它會去 Eureka 註冊中心拉取服務列表, 如果發現有指定的路由規則, 就會按照規則路由到相應的服務介面上去.
zuul 路由配置:
單例項 ServiceId 對映: 上例中我們使用了
zuul:
routes:
clienta:
path: /client/** #凡是 /client 開頭的 HTTP 請求, 都會被對映到 clientA 這個服務例項上去, 假設 clientA 的服務例項的埠為 8081
# 那麼 http://localhost:8080/client/getuser 的請求,在內部會呼叫對映的地址 http://localhost:8081/getuser 進行服務
# 請求, 獲取到結果,然後返回
serviceId: eureka-clientA #服務例項 ID
其實這個寫法可以簡化成這樣 (第一種寫法)
zuul:
routes:
eureka-clientA: /client/**
還可以簡化成這樣, 對映規則與 ServiceId 都不用寫 (第二種寫法):
zuul:
routes:
eureka-clientA:
在這種情況下, Zuul 會為 eureka-clientA 新增一個預設的對映規則 /client-clienta/** , 也就是相當於 下面這樣寫
zuul:
routes:
eureka-clientA:
path: /eureka-clienta/**
serviceId: eureka-clientA
單例項 url 對映
除了路由到服務之外, 還可以路由到具體的地址, 只需要將 serviceId 替換成 url 就可以了.如下所示:
#對映到具體的url
zuul:
routes:
eureka-clientA:
path: /client/**
url: http://localhost:8081 # eureka-clientA 所在的地址
多例項路由
在預設情況下 Zuul 會使用 Eureka 中整合的負載均衡功能, 如果想要使用 Ribbon 的輔助均衡功能, 就需要指定一個 serviceId, 此操作需要禁止 Ribbon 使用 Eureka. 在 E 版之後,新增了負載均衡策略的配置,如下
zuul:
routes:
eureka-clientA:
path: /client/**
serviceId: eureka-clientA #服務例項ID
ribbon:
eureka:
enabled: false #禁止 ribbon 使用eureka
#為服務例項ID 配置 負載均衡策略
eureka-clientA:
ribbon:
NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList #用於獲取服務例項列表的類
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #負載均衡策略
listOfServers: localhost:8081, localhost:8082, localhost:8083 # 所有可用的服務提供者的服務例項地址,
然後在 eureka-clientA 服務提供者個工程的對外介面中增加一個方法,
@RequestMapping("/gethost")
public String getHostAndPort(HttpServletRequest request) {
return String.valueOf(request.getServerPort()); //獲取埠
}
然後依次eureka 註冊中心, 並且以不同的埠啟動 eureka-clientA 服務的3 個例項, 然後在啟動 zuul , 然後在瀏覽器訪問:http://localhost:8080/client/gethost 可以看到
這幾個埠隨機出現, 說明使用了我們定義好的 隨機策略. 至於為什麼要 Ribbon 禁用 Eureka , 這個還在研究當中
forward 本地跳轉
有時候我們在 zuul 中做一些邏輯處理, 我們希望在訪問 /client 介面的時候轉到指定的方法上來處理, 就需要用到 zuul 的本地跳轉,如:
修改 zuul 配置檔案
# zuul 配置本地轉發, 當請求/client 開頭的介面時, 會被 API閘道器轉到到 API 閘道器中以 /client 為字首的請求上
# 假如: 當API 閘道器接收到 /client/add 之類的請求的時候, 因為符合規則, 所以請求會被 API 閘道器轉發到閘道器的
# /client/add 上.
zuul:
routes:
eureka-clientA:
path: /client/** #當符合該路由規則的請求到達時, 會被 API閘道器轉到到 API 閘道器中以 /client 為字首的請求上
# 個人理解是, 本地轉發的時候, 會用 本地轉發指定的字首, 替換掉 路由匹配的字首, 然後把後面的地址
#在拼接起來, 如, 請求 http://localhost:8080/client/a/b/c/getuser ,因為符合路由規則, 所以會轉發到
# zuul 中的 http://localhost:8080/user/a/b/c/getuser.
url: forward:/user
在 zuul 中新增一個對外介面類, 根據上面的描述 介面的地址必須為: /user/add 否則會報 404 錯誤
@RestController
public class TestControlelr {
@RequestMapping("/user/add")
public String add(Integer a, Integer b) {
return "本地跳轉" + (a + b);
}
}
啟動服務, 然後訪問 http://localhost:8080/client/add?a=10&b=20 可以看到輸出, 說明本地跳轉成了,
如果將 zuul 閘道器中的介面地址改成了 /user, 然後在訪問介面, 這個時候本地轉發就會錯誤了.
所以要按照規則處理.
如果有這樣一種配置:
zuul:
routes:
eureka-clientA:
path: /client/**
serviceId: eureka-clientA
eureka-clientB:
path: /client/**
serviceId: eureka-clientB
路由規則是一樣的, 經過測試, 它總是會匹配到配置檔案後面的那個服務上去. 這裡是 eureka-clientB 服務上.yml直譯器在 工作的時候, 如果同一個對映對應多個服務, 按照載入順序, 最末尾載入的對映會覆蓋前面載入的對映.
zuul 還可以配置路由字首, 服務遮蔽, 路徑遮蔽, 敏感頭資訊(也就是切換敏感資訊與下游伺服器的互動),配置重定向(比如隱藏真實服務的 IP, 暴露zuul 閘道器的IP), 重試機制等.
參考<重新定義 Spring Cloud> 一書