SpringCloud--08、閘道器Zuul
1、概述
Zuul 作為路由閘道器元件、隸屬Netfix
SpringCloud微服務架構圖:
通過圖可以看到、zuul是整個架構對外的大門、pc端和移動端的請求都要通過Zuul這個閘道器、然後由網站
來實現鑑權、動態路由等操作、
2、工作原理
話不多說、先來個圖
Zuul 是通過Servlet 來實現的, Zuul 通過自定義的Zuu!Servlet 來對請求進行控制。
Zuul 的核心是一系列過濾器,可以在Http 請求的發起和響應返回期間執行一系列的過濾器。
Zuul 包括以下4 種過濾器:
口PRE 過濾器: 它是在請求路由到具體的服務之前執行的,這種型別的過濾器可以做安全驗證,例如身份驗證、引數驗證等。 口ROUTING 過濾器: 它用於將請求路由到具體的微服務例項。在預設情況下,它使用Http Client 進行網路請求。 口POST 過濾器:它是在請求己被路由到微服務後執行的。一般情況下,用作收集統計資訊、指標,以及將響應傳輸到客戶端。 口ERROR 過濾器:它是在其他過濾器發生錯誤時執行的。
3、入門案例
依賴pom.xml
<?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"> <parent> <artifactId>cloud-demo</artifactId> <groupId>cn.itcast.demo</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>cn.itcast.demo</groupId> <artifactId>zuul-demo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> </project>
啟動類ZuulDemoApp.java
package www.baidus.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * @ Author :ShaoWei Sun. * @ Date :Created in 13:47 2018/12/2 */ @SpringBootApplication @EnableZuulProxy //開啟Zuul的閘道器功能 @EnableDiscoveryClient //開啟Eureka客客戶端發現功能 public class ZuulDemoApp { public static void main(String[] args) { SpringApplication.run(ZuulDemoApp.class,args); } }
application.yml全域性配置
server:
port: 10010 #服務埠
spring:
application:
name: api-gateway #指定服務名
編寫路由規則
zuul:
routes:
user-service: # 這裡是路由id,隨意寫
path: /user-service/** # 這裡是對映路徑
url: http://127.0.0.1:8081 # 對映路徑對應的實際url地址
我們將符合path 規則的一切請求,都代理到 url引數指定的地址
本例中,我們將 /user-service/**開頭的請求,代理到http://127.0.0.1:8081
啟動測試:http://127.0.0.1:10010/user-service/user/1
4、動態路由Eureka註冊中心例項列表(面向服務的路由)
上面的例項中、(將 /user-service/**開頭的請求,代理到http://127.0.0.1:8081)
我們把路徑對應的服務地址寫死了!如果同一服務有多個例項的話,這樣做顯然就不合理了。
我們應該根據服務的名稱,去Eureka註冊中心查詢 服務對應的所有例項列表,然後進行動態路由才對!
在zuul-service中pom.xml新增Eureka客戶端依賴、使閘道器通過Eureka動態路由到(負載小的)伺服器上。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
zuul-service開啟Eureka客戶端發現功能
package www.baidus.zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @ Author :ShaoWei Sun.
* @ Date :Created in 13:47 2018/12/2
*/
@SpringBootApplication
@EnableZuulProxy //開啟Zuul的閘道器功能
@EnableDiscoveryClient //開啟Eureka客客戶端發現功能
public class ZuulDemoApp {
public static void main(String[] args) {
SpringApplication.run(ZuulDemoApp.class,args);
}
}
新增Eureka配置,獲取服務資訊 、修改對映檔案application.yml
eureka:
client:
registry-fetch-interval-seconds: 5 # 獲取服務列表的週期:5s
service-url:
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka,http://127.0.0.1:10088/eureka,
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
修改對映配置,通過服務名稱獲取
zuul:
routes:
user-service: # 這裡是路由id,隨意寫
path: /user-serverss/** # 這裡是對映路徑
serviceId: user-service # 指定服務名稱
啟動測試:http://127.0.0.1:10010/user-serverss/user/1 檢視日誌
5、簡化的路由配置
zuul:
routes:
user-service: /user-service/** # 這裡是對映路徑
6、預設路由規則
預設情況下,一切服務的對映路徑就是服務名本身。
- 例如服務名為:user-service,則預設的對映路徑就是:/user-service/**
7、路由前緣
zuul:
prefix: /ap # 新增路由字首
routes:
user-service: /user-service/** # 這裡是對映路徑
路徑/ap/user-service/user/1
將會被代理到/user-service/user/1
8、過慮器、自定義過慮器
Zuul作為閘道器的其中一個重要功能,就是實現請求的鑑權。而這個動作我們往往是通過Zuul提供的過濾器來實現的。
ZuulFilter是過濾器的頂級父類。在這裡我們看一下其中定義的4個最重要的方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 來自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
說明:
filterType:返回字串,代表過濾器的型別。包含以下4種:
- pre:請求在被路由之前執行
- routing:在路由請求時呼叫
- post:在routing和errror過濾器之後呼叫
- error:處理請求時發生錯誤呼叫
filterOrder:通過返回的int值來定義過濾器的執行順序,數字越小優先順序越高。
shouldFilter:返回一個Boolean值,判斷該過濾器是否需要執行。返回true執行,返回false不執行。
run:過濾器的具體業務邏輯。
過濾器執行生命週期:圖解
正常流程:
請求到達首先會經過pre型別過濾器,而後到達routing型別,進行路由,
請求就到達真正的服務提供者,執行請求,返回結果後,會到達post過濾器。而後返回響應。
異常流程:
- 整個過程中,pre或者routing過濾器出現異常,都會直接進入error過濾器,
再error處理完畢後,會將請求交給POST過濾器,最後返回給使用者。
- 如果是error過濾器自己出現異常,最終也會進入POST過濾器,而後返回。
- 如果是POST過濾器出現異常,會跳轉到error過濾器,但是與pre和routing不同的時,
請求不會再到達POST過濾器了。
使用場景:
- 請求鑑權:一般放在pre型別,如果發現沒有訪問許可權,直接就攔截了
- 異常處理:一般會在error型別和post型別過濾器中結合來處理。
- 服務呼叫時長統計:pre和post結合使用。
自定義過濾器:如果請求引數中沒token就攔截不放行。
MyFilter.java
package www.baidus.zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @ Author :ShaoWei Sun.
* @ Date :Created in 14:15 2018/12/4
*/
@Component
public class MyFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//獲取上下文資源
RequestContext currentContext = RequestContext.getCurrentContext();
//從上下文獲取request
HttpServletRequest request = currentContext.getRequest();
//獲取引數
String token = request.getParameter("token");
if (StringUtils.isEmpty(token) || "".equals(token.trim())){
// 沒有token,登入校驗失敗,攔截不響應
currentContext.setSendZuulResponse(false);
//返回401狀態碼
currentContext.setResponseStatusCode(401);
}
return null;
}
}
http://127.0.0.1:10010/ap/user-service/user/2
http://127.0.0.1:10010/ap/user-service/user/2?token=lan
9、負載均衡和熔斷
Zuul中預設就已經集成了Ribbon負載均衡和Hystix熔斷機制。但是所有的超時策略都是走的預設值,
比如熔斷超時時間只有1S,很容易就觸發了。因此建議我們手動進行配置:
ribbon:
ConnectTimeout: 250 # 連線超時時間(ms)
ReadTimeout: 2000 # 通訊超時時間(ms)
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000