1.Zuul簡介
zuul是從設備和網站到後端應用程序所有請求的前門,爲內部服務提供可配置的對外URL到服務的映射關係,基於JVM的後端路由器。爲所有下游服務的網關層,所有前臺請求均先達到zuul服務,而後在由zuul路由至下游服務
2.Zuul工作原理
Zuul是通過Servlet實現的,Zuul通過自定義的ZuulServlet來對請求進行控制,Zuul核心是一系列過濾器,可以在Http請求的發起和響應返回期間執行一系列的過濾器,Zuul包括如下4種過濾器:
(1) PRE過濾器: 它是請求具體實現邏輯前過濾,這種類型的過濾器可以做安全驗證,如身份驗證,參數驗證等
(2) ROUTING過濾器: 它用於將請求路由到具體的微服務實例。默認,它使用Http Client進行網絡請求
(3) POST過濾器: 它是處理具體邏輯後,返回前端前過濾
(4) ERROR過濾器: 它是在其它過濾器發生錯誤時執行的
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
// 省略代碼
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
分析上述代碼:
(1) 最先執行preRoute(),當出現異常時,執行error()和postRoute()
(2) 執行route(),當出現異常時,執行error()和postRoute()
(3)最後執行postRoute(),當出現異常時,執行error(),而不執行postRoute()
Zuul是通過RequestContext對象來共享數據的,每個請求都會創建一個RequestContext對象,每個過濾器包括如下幾個特性:
(1) FilterType: 表示過濾器類型
(2) FilterOrder:執行順序,值越小,越先執行
(3) shouldFilter: 表示是否繼續執行run中邏輯,如果爲false則表示不執行
(4) run: 執行具體的過濾細節
3.入門案例
服務名 | 端口 | 用途 |
---|---|---|
eureka-ribbon-client | 8764 | ribbon客戶端 |
eureka-client | 8762、8763 | 服務提供者 |
eureka-feign-client | 8765 | feign客戶端 |
eureka-zuul-client | 5000 | zuul客戶端 |
consul | 8500 | 註冊中心 |
- 創建eureka-zuul-client服務
- 引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 書寫啓動類,添加註解
@EnableZuulProxy
@EnableZuulProxy
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaZuulClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaZuulClientApplication.class, args);
}
}
- 配置application.yml中路由規則
zuul:
routes:
hiapi:
path: /hiapi/**
serviceId: eureka-client
ribbonapi:
path: /ribbonapi/**
serviceId: eureka-ribbon-client
feignapi:
path: /feignapi/**
serviceId: eureka-feign-client
-
訪問服務
(1) 訪問
http://localhost:8766/hiapi/hi?name=lisi
結果如下hi lisi, i am from port:8762
hi lisi, i am from port:8763
(2) 訪問
http://localhost8766/ribbonapi/hi?name=lisi
結果與上邊相同(3) 訪問
http://localhost8766/feignapi/hi?name=lisi
結果與上邊相同
4.Zuul其它操作
-
在Zuul上配置熔斷器
(1) 在Zuul中實現熔斷功能需要實現 ZuulFallbackProvider接口
(2) getRoute()方法:用於確定具體路由服務
(3) fallbackResponse():返回信息處理
@Component
public class MyZuulFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "eureka-client";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 0;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("sorroy,have error".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return httpHeaders;
}
};
}
}
-
自定義ZuulFilter
自定義ZuulFilter需要集成ZuulFilter,重寫其filterTyepe()、filterOrder()、shouldFilter()、run()方法,以下自定義ZuulFilter用於判定是否攜帶token
方法 作用 shouldFilter 表示是否執行run()方法,false:否,true:是 RequetContext 每個請求均會維護一個RequestContext setSendZuulResponse 表示是否執行下一個過濾器,false:否,true:是 代碼演示:
@Component
public class MyFilter extends ZuulFilter {
private Logger logger = LoggerFactory.getLogger(MyFilter.class);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String token = request.getParameter("token");
if(Objects.isNull(token)){
logger.warn("token不存在");
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(401);
try {
currentContext.getResponse().getWriter().write("token is empty");
} catch (IOException e) {
e.printStackTrace();
}
}
logger.info("OK");
return null;
}
}
5.Zuul常見使用方式
-
每個平臺使用獨立的Zuul網關層,從而使Zuul集羣化,以下截圖來自《深入理解Spring Cloud與微服務構建》
-
通過Nginx根據負載均衡策略將請求路由至Zuul集羣,以下截圖來自《深入理解Spring Cloud與微服務構建》
6.Zuul常見配置
規則 | 解釋 | 例子 |
---|---|---|
/** | 匹配任意路徑及字符 | /provider/a,/provider/bbb,/provider/aa/bb/cc |
/* | 匹配任意數量字符,只有一層路徑 | /provider/a,/provider/bbb,/provider/ccdc |
/? | 匹配單個字符,一層路徑 | /provider/a,/provider/c |
- 自定義指定服務訪問路徑
path:表示請求前綴路徑
serviceId:表示服務名
zuul:
routes:
provider-service:
path: /provider/**
serviceId: provider-service
- 簡化寫法
自動會將服務映射至
provider-service
zuul:
routes:
provider-service: /provider/**
- 另一種寫法
path與serviceId均不需要,自動映射,此種情況下,則會自動生成path = /provider-service/**,serviceId = provdier-service
zuul:
routes:
provider-service:
- 直接配置服務ip:port
url:服務ip:port
zuul:
routes:
provider-service:
ptah: /provider/**
url: http://localhost:8000
- forward本地跳轉
url: forward:/本地請求路徑,當請求/provider路徑時,會跳轉至zuul服務本地的路徑前綴爲/provider的接口,如url:
http://localhost:7000/provider/add?a=10&b=20
,則會自動匹配zuul服務中的/provider/add服務
zuul:
routes:
provider-service:
path: /provider/**
url: forward:/provider
- 相同路徑,後者覆蓋前者
如下配置,則會請求至provider-service-b服務
zuul:
routes:
provider-service-a:
path: /provider/**
serviceId: provider-service-a
provider-service-b:
path: /provider/**
serviceId: provider-service-b
- 使用指定前綴
prefix: /pre,表示請求前綴爲pre,但實際請求路徑爲/provider/,可以通過stripPrefix=false關閉此功能,則啓作用的路徑爲/pre/provider/,一般不配置功能
zuul:
prefix: /pre
routes:
provider-a: /provider/**
- 服務屏蔽及路徑屏蔽
ignored-service:屏蔽服務,當請求該服務時,會報404不存在
ignored-patterns:屏蔽路徑
zuul:
ignored-service: provider-b
ignored-patterns: /provider-b/**
prefix: /pre
routes:
provider-a: /provider-a/**
- 敏感頭信息
在構建系統時,有許多請求頭信息不需要傳遞至下層服務,則可在zuul層進行攔截,而不傳遞
zuul:
routes:
provider-service:
path: /provider/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
serviceId: provider-service
- 重試機制
在生產環境中,可能存在請求失敗,需重試情況,爲了用戶無感知,可通過zuul設置重試,但要考慮服務冪等性問題
zuul:
retryable: true # 開啓重試
ribbon:
MaxAutoRetries: 1 # 同一個服務重試的次數
MaxAutoRetriesNextServer: 1 # 切換相同服務數量
- 自定義請求路由
通過上述路由配置我們知道,當只服務服務,不配置路徑及serviceId時,則會默認生成path:/provider-service/**,servcieId: provider-service,但有時候我們想自定義生成的路徑,則可採用如下方式,定義返回的格式爲{name}
@Bean
public PatternServiceRouteMapper serviceRouteMapper(){
return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
}
7.參考資料
- 《重新定義Springcloud實戰》
- 《Springcloud微服務實戰》
- 《深入理解Spring Cloud與微服務構建》