路由熔斷
當我們的后端服務出現異常的時候,我們不希望將異常拋出給最外層,期望服務可以自動進行一降級。Zuul給我們提供了這樣的支持。當某個服務出現異常時,直接返回我們預設的信息。
如果沒有配置fallback,zuul調用時超時了,


我們通過自定義的fallback方法,並且將其指定給某個route來實現該route訪問出問題的熔斷處理。主要繼承ZuulFallbackProvider接口來實現,ZuulFallbackProvider默認有兩個方法,一個用來指明熔斷攔截哪個服務,一個定制返回內容。
public interface FallbackProvider extends ZuulFallbackProvider { /** * Provides a fallback response based on the cause of the failed execution. * * @param cause cause of the main method failure * @return the fallback response */ ClientHttpResponse fallbackResponse(Throwable cause); }
實現類通過實現getRoute方法,告訴Zuul它是負責哪個route定義的熔斷。而fallbackResponse方法則是告訴 Zuul 斷路出現時,它會提供一個什么返回值來處理請求。
后來Spring又擴展了此類,豐富了返回方式,在返回的內容中添加了異常信息,因此最新版本建議直接繼承類FallbackProvider 。
我們以上面的spring-cloud-producer服務為例,定制它的熔斷返回內容。
package com.dxz.zuul.fallback; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.stereotype.Component; @Component public class ProducerFallback implements FallbackProvider { private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class); // 指定要處理的 service。 @Override public String getRoute() { return "service-producter"; } 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(); } }
路由轉發配置:
zuul.routes.api-test.path: /api-test/** zuul.routes.consuer.path: /service-consumer/** zuul.routes.consuer.serviceId: service-consumer zuul.routes.producter.path: /service-producter/** zuul.routes.producter.serviceId: service-producter
為了便於演示,將zuul里的超時配置短些:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 6000 hystrix.command.default.execution.timeout.enabled: true feign.hystrix.enabled: true spring.cloud.loadbalancer.retry.enabled: true ribbon.ReadTimeout: 6000 ribbon.ConnectTimeout: 6000
訪問: http://127.0.0.1:8091/service-producter/book/getbook5/2?token=1

當服務出現異常時,打印相關異常信息,並返回”The service is unavailable.”。
Zuul 目前只支持服務級別的熔斷,不支持具體到某個URL進行熔斷。
路由重試
有時候因為網絡或者其它原因,服務可能會暫時的不可用,這個時候我們希望可以再次對服務進行重試,Zuul也幫我們實現了此功能,需要結合Spring Retry 一起來實現。下面我們以上面的項目為例做演示。
添加Spring Retry依賴
首先在spring-cloud-zuul項目中添加Spring Retry依賴。
compile 'org.springframework.retry:spring-retry:1.2.2.RELEASE'
開啟Zuul Retry
再配置文件中配置啟用Zuul Retry
#是否開啟重試功能 zuul.retryable=true #對當前服務的重試次數 ribbon.MaxAutoRetries=2 #切換相同Server的次數 ribbon.MaxAutoRetriesNextServer=0
這樣我們就開啟了Zuul的重試功能。
測試
我們對service-producer進行改造,在getbook5方法中添加定時,並且在請求的一開始打印參數。
@RestController @RequestMapping("/book") public class BookProducter { @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/getbook5/{id}", method = RequestMethod.GET) public Book getbook5(@ApiParam("id編號") @PathVariable("id") Integer id) throws InterruptedException { System.out.println(">>>>>>>>/getbook5/" + id); int i = new Random().nextInt(20); TimeUnit.SECONDS.sleep(i); System.out.println("SLEEP=" + i + ">>>>>>>>/getbook5/" + id); if (id == 1) { return new Book(id, "《李自成》", 55, "姚雪垠", "人民文學出版社"); } else if (id == 2) { return new Book(id, "中國文學簡史", 33, "林庚", "清華大學出版社"); } return new Book(id, "文學改良芻議", 33, "胡適", "無"); } }
重啟 service-producter和zuul-demo項目。
訪問地址:http://127.0.0.1:8091/service-producter/book/getbook5/3?token=1,當頁面返回:The service is unavailable.時查看項目service-producter后台日志如下:

說明進行了三次的請求,也就是進行了兩次的重試。這樣也就驗證了我們的配置信息,完成了Zuul的重試功能。
注意
開啟重試在某些情況下是有問題的,比如當壓力過大,一個實例停止響應時,路由將流量轉到另一個實例,很有可能導致最終所有的實例全被壓垮。說到底,斷路器的其中一個作用就是防止故障或者壓力擴散。用了retry,斷路器就只有在該服務的所有實例都無法運作的情況下才能起作用。這種時候,斷路器的形式更像是提供一種友好的錯誤信息,或者假裝服務正常運行的假象給使用者。
不用retry,僅使用負載均衡和熔斷,就必須考慮到是否能夠接受單個服務實例關閉和eureka刷新服務列表之間帶來的短時間的熔斷。如果可以接受,就無需使用retry。
