時間過的很快,寫springcloud(十):服務網關zuul初級篇還在半年前,如今已是2018年了,咱們繼續探討Zuul更高級的使用方式。html
上篇文章主要介紹了Zuul網關使用模式,以及自動轉發機制,但其實Zuul還有更多的應用場景,好比:鑑權、流量轉發、請求統計等等,這些功能均可以使用Zuul來實現。前端
Filter是Zuul的核心,用來實現對外服務的控制。Filter的生命週期有4個,分別是「PRE」、「ROUTING」、「POST」、「ERROR」,整個生命週期能夠用下圖來表示。java
Zuul大部分功能都是經過過濾器來實現的,這些過濾器類型對應於請求的典型生命週期。git
類型 | 順序 | 過濾器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 標記處理Servlet的類型 |
pre | -2 | Servlet30WrapperFilter | 包裝HttpServletRequest請求 |
pre | -1 | FormBodyWrapperFilter | 包裝請求體 |
route | 1 | DebugFilter | 標記調試標誌 |
route | 5 | PreDecorationFilter | 處理請求上下文供後續使用 |
route | 10 | RibbonRoutingFilter | serviceId請求轉發 |
route | 100 | SimpleHostRoutingFilter | url請求轉發 |
route | 500 | SendForwardFilter | forward請求轉發 |
post | 0 | SendErrorFilter | 處理有錯誤的請求響應 |
post | 1000 | SendResponseFilter | 處理正常的請求響應 |
禁用指定的Filtergithub
能夠在application.yml中配置須要禁用的filter,格式:spring
zuul:
FormBodyWrapperFilter:
pre:
disable: true
複製代碼
實現自定義Filter,須要繼承ZuulFilter的類,並覆蓋其中的4個方法。後端
public class MyFilter extends ZuulFilter {
@Override
String filterType() {
return "pre"; //定義filter的類型,有pre、route、post、error四種
}
@Override
int filterOrder() {
return 10; //定義filter的順序,數字越小表示順序越高,越先執行
}
@Override
boolean shouldFilter() {
return true; //表示是否須要執行該filter,true表示執行,false表示不執行
}
@Override
Object run() {
return null; //filter須要執行的具體操做
}
}
複製代碼
咱們假設有這樣一個場景,由於服務網關應對的是外部的全部請求,爲了不產生安全隱患,咱們須要對請求作必定的限制,好比請求中含有Token便讓請求繼續往下走,若是請求不帶Token就直接返回並給出提示。安全
首先自定義一個Filter,在run()方法中驗證參數是否含有Token。bash
public class TokenFilter extends ZuulFilter {
private final Logger logger = LoggerFactory.getLogger(TokenFilter.class);
@Override
public String filterType() {
return "pre"; // 能夠在請求被路由以前調用
}
@Override
public int filterOrder() {
return 0; // filter執行順序,經過數字指定 ,優先級爲0,數字越大,優先級越低
}
@Override
public boolean shouldFilter() {
return true;// 是否執行該過濾器,此處爲true,說明須要過濾
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("token");// 獲取請求的參數
if (StringUtils.isNotBlank(token)) {
ctx.setSendZuulResponse(true); //對請求進行路由
ctx.setResponseStatusCode(200);
ctx.set("isSuccess", true);
return null;
} else {
ctx.setSendZuulResponse(false); //不對其進行路由
ctx.setResponseStatusCode(400);
ctx.setResponseBody("token is empty");
ctx.set("isSuccess", false);
return null;
}
}
}
複製代碼
將TokenFilter加入到請求攔截隊列,在啓動類中添加如下代碼:網絡
@Bean
public TokenFilter tokenFilter() {
return new TokenFilter();
}
複製代碼
這樣就將咱們自定義好的Filter加入到了請求攔截中。
測試
咱們依次啓動示例項目:spring-cloud-eureka
、spring-cloud-producer
、spring-cloud-zuul
,這個三個項目均爲上一篇示例項目,spring-cloud-zuul
稍微進行改造。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo
,返回:token is empty ,請求被攔截返回。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx
,返回:hello neo,this is first messge,說明請求正常響應。
經過上面這例子咱們能夠看出,咱們可使用「PRE"類型的Filter作不少的驗證工做,在實際使用中咱們能夠結合shiro、oauth2.0等技術去作鑑權、驗證。
當咱們的後端服務出現異常的時候,咱們不但願將異常拋出給最外層,指望服務能夠自動進行一降級。Zuul給咱們提供了這樣的支持。當某個服務出現異常時,直接返回咱們預設的信息。
咱們經過自定義的fallback方法,而且將其指定給某個route來實現該route訪問出問題的熔斷處理。主要繼承ZuulFallbackProvider接口來實現,ZuulFallbackProvider默認有兩個方法,一個用來指明熔斷攔截哪一個服務,一個定製返回內容。
public interface ZuulFallbackProvider {
/** * The route this fallback will be used for. * @return The route the fallback will be used for. */
public String getRoute();
/** * Provides a fallback response. * @return The fallback response. */
public ClientHttpResponse fallbackResponse();
}
複製代碼
實現類經過實現getRoute方法,告訴Zuul它是負責哪一個route定義的熔斷。而fallbackResponse方法則是告訴 Zuul 斷路出現時,它會提供一個什麼返回值來處理請求。
後來Spring又擴展了此類,豐富了返回方式,在返回的內容中添加了異常信息,所以最新版本建議直接繼承類FallbackProvider
。
咱們以上面的spring-cloud-producer服務爲例,定製它的熔斷返回內容。
@Component
public class ProducerFallback implements FallbackProvider {
private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);
//指定要處理的 service。
@Override
public String getRoute() {
return "spring-cloud-producer";
}
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("The service is unavailable.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
@Override
public ClientHttpResponse fallbackResponse(Throwable cause) {
if (cause != null && cause.getCause() != null) {
String reason = cause.getCause().getMessage();
logger.info("Excption {}",reason);
}
return fallbackResponse();
}
}
複製代碼
當服務出現異常時,打印相關異常信息,並返回"The service is unavailable."。
啓動項目spring-cloud-producer-2,這時候服務中心會有兩個spring-cloud-producer項目,咱們重啓Zuul項目。再手動關閉spring-cloud-producer-2項目,屢次訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx
,會交替返回:
hello neo,this is first messge
The service is unavailable.
...
複製代碼
根據返回結果能夠看出:spring-cloud-producer-2項目已經啓用了熔斷,返回:The service is unavailable.
Zuul 目前只支持服務級別的熔斷,不支持具體到某個URL進行熔斷。
有時候由於網絡或者其它緣由,服務可能會暫時的不可用,這個時候咱們但願能夠再次對服務進行重試,Zuul也幫咱們實現了此功能,須要結合Spring Retry 一塊兒來實現。下面咱們以上面的項目爲例作演示。
添加Spring Retry依賴
首先在spring-cloud-zuul項目中添加Spring Retry依賴。
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
複製代碼
開啓Zuul Retry
再配置文件中配置啓用Zuul Retry
#是否開啓重試功能
zuul.retryable=true
#對當前服務的重試次數
ribbon.MaxAutoRetries=2
#切換相同Server的次數
ribbon.MaxAutoRetriesNextServer=0
複製代碼
這樣咱們就開啓了Zuul的重試功能。
測試
咱們對spring-cloud-producer-2進行改造,在hello方法中添加定時,而且在請求的一開始打印參數。
@RequestMapping("/hello")
public String index(@RequestParam String name) {
logger.info("request two name is "+name);
try{
Thread.sleep(1000000);
}catch ( Exception e){
logger.error(" hello two error",e);
}
return "hello "+name+",this is two messge";
}
複製代碼
重啓 spring-cloud-producer-2和spring-cloud-zuul項目。
訪問地址:http://localhost:8888/spring-cloud-producer/hello?name=neo&token=xx
,當頁面返回:The service is unavailable.
時查看項目spring-cloud-producer-2後臺日誌以下:
2018-01-22 19:50:32.401 INFO 19488 --- [io-9001-exec-14] o.s.c.n.z.f.route.FallbackProvider : request two name is neo
2018-01-22 19:50:33.402 INFO 19488 --- [io-9001-exec-15] o.s.c.n.z.f.route.FallbackProvider : request two name is neo
2018-01-22 19:50:34.404 INFO 19488 --- [io-9001-exec-16] o.s.c.n.z.f.route.FallbackProvider : request two name is neo
複製代碼
說明進行了三次的請求,也就是進行了兩次的重試。這樣也就驗證了咱們的配置信息,完成了Zuul的重試功能。
注意
開啓重試在某些狀況下是有問題的,好比當壓力過大,一個實例中止響應時,路由將流量轉到另外一個實例,頗有可能致使最終全部的實例全被壓垮。說到底,斷路器的其中一個做用就是防止故障或者壓力擴散。用了retry,斷路器就只有在該服務的全部實例都沒法運做的狀況下才能起做用。這種時候,斷路器的形式更像是提供一種友好的錯誤信息,或者僞裝服務正常運行的假象給使用者。
不用retry,僅使用負載均衡和熔斷,就必須考慮到是否可以接受單個服務實例關閉和eureka刷新服務列表之間帶來的短期的熔斷。若是能夠接受,就無需使用retry。
咱們實際使用Zuul的方式如上圖,不一樣的客戶端使用不一樣的負載將請求分發到後端的Zuul,Zuul在經過Eureka調用後端服務,最後對外輸出。所以爲了保證Zuul的高可用性,前端能夠同時啓動多個Zuul實例進行負載,在Zuul的前端使用Nginx或者F5進行負載轉發以達到高可用性。
參考:
Spring Cloud(七)服務網關 Zuul Filter 使用
Spring Cloud技術分析(4)- spring cloud zuul
Zuul 路由使用