Zuul-路由熔断与重试
当后端服务出现异常的时候,我们不希望将异常直接抛出给最外层,期望服务可以自动进行降级。 Zuul给我们提供了这样的支持,当某个服务出现异常时,直接返回我们预设的信息。
通过自定义的fallback方法,并且将其指定给某个route来实现该route访问出问题的熔断处理。主要继承FallbackProvider接口来实现,FallbackProvider默认有两个方法,getRoute方法用来指明熔断拦截的服务,fallbackResponse方式用来定制返回内容。
路由熔断降级fallback
@Component
public class MyFallBack implements FallbackProvider {
/**
* 需要熔断降级的服务名称,如果是对所有的服务,可以写“*”
* @return
*/
@Override
public String getRoute() {
return "microservice-provider";
}
/**
* 针对微服务进行降级的fallback处理
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, final 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 status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallback:" + MyFallBack.this.getRoute()).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
访问http://localhost:6001/xyz/mydept/provider/list返回fallback:microservice-provider
路由重试
- pom增加依赖
<!--API网关重试-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
- 修改application.yml,开始重试配置
zuul:
retryable: true #是否开始重试
ribbon:
MaxAutoRetries: 3 #对当前服务的重试次数
MaxAutoRetriesNextServer: 0 #切换相同server的次数
- 修改provider-8002的服务,通过sleep的方式模拟服务不返回
@RestController
@Log4j2
public class ProviderDeptController {
@Autowired
private ProviderDeptService providerDeptService;
@RequestMapping("/provider/list")
public Dept list() {
return providerDeptService.list();
}
/**
* @description 模拟服务不返回
*/
@RequestMapping("/zuul/retry")
public String zuulRetry() throws InterruptedException {
log.info("come info zuul retry ...");
Thread.sleep(10000);
return "retry sucess ..";
}
}
- 测试
http://localhost:6001/xyz/mydept/zuul/retry,8002服务的日志显示
2020-03-20 18:18:12.451 INFO 13584 --- [nio-8002-exec-5] c.xyz.controller.ProviderDeptController : come info zuul retry ...
2020-03-20 18:18:13.458 INFO 13584 --- [nio-8002-exec-8] c.xyz.controller.ProviderDeptController : come info zuul retry ...
2020-03-20 18:18:14.468 INFO 13584 --- [nio-8002-exec-7] c.xyz.controller.ProviderDeptController : come info zuul retry ...
2020-03-20 18:18:15.472 INFO 13584 --- [nio-8002-exec-6] c.xyz.controller.ProviderDeptController : come info zuul retry ...
注意
,比如当压力过大,一个实例停止响应时,路由将流量转到另一个实例,很有可能导致最终所有的实例全被压垮。说到底,断路器的其中一个作用就是防止故障或者压力扩散。用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。这种时候,断路器的形式更像是提供一种友好的错误信息,或者假装服务正常运行的假象给使用者。
不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。
代码实例-github
参考文档