SpringCloud | Zuul(4)-路由熔断和重试

1,411 阅读3分钟

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

路由重试

  1. pom增加依赖
        <!--API网关重试-->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
  1. 修改application.yml,开始重试配置
zuul:
  retryable: true  #是否开始重试

ribbon:
  MaxAutoRetries: 3 #对当前服务的重试次数
  MaxAutoRetriesNextServer: 0 #切换相同server的次数
  1. 修改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 ..";
    }

}
  1. 测试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 ...

注意

\color{red}{开启重试在某些情况下是有问题的},比如当压力过大,一个实例停止响应时,路由将流量转到另一个实例,很有可能导致最终所有的实例全被压垮。说到底,断路器的其中一个作用就是防止故障或者压力扩散。用了retry,断路器就只有在该服务的所有实例都无法运作的情况下才能起作用。这种时候,断路器的形式更像是提供一种友好的错误信息,或者假装服务正常运行的假象给使用者。 不用retry,仅使用负载均衡和熔断,就必须考虑到是否能够接受单个服务实例关闭和eureka刷新服务列表之间带来的短时间的熔断。如果可以接受,就无需使用retry。

代码实例-github

参考文档