【Spring Cloud总结】29.Zuul的FallBack回退机制

接上篇《28.Zuul的Filter过滤器》  Spring Cloud版本为Finchley.SR2版

上一篇我们介绍了有关Zuul的Filter过滤器禁用的相关知识,本篇我们来继续学习有关Zuul回退的相关知识。
本部分官方文档:https://cloud.spring.io/spring-cloud-static/Finchley.SR4/single/spring-cloud.html#hystrix-fallbacks-for-routes
注:好像Finchley.SR2的文档已经挂了,最新的是Finchley.SR4的文档。

一、Zuul的代理服务断路器触发试验

通过前面的几个章节我们知道,在默认情况下,经过Zuul的请求都会被Hystrix包裹,即Zuul的所有请求在Hystrix Command中执行,所以Zuul本身就具有断路器的功能。

我们在讲解Zuul的退回机制之前,我们前做一个断路器触发的试验。

首先启动我们之前搭建的Eureka Server、User微服务以及Zuul服务:

然后之前也在Zuul中配置了User的别名映射:

zuul:
  ignored-services: '*'
  routes:
    microserver-provider-user: /user/**

然后我们通过Zuul网关代理访问User服务,是没问题的:

因为经过Zuul的请求都会被Hystrix包裹,那么我们直接访问Zuul的Hystrix端点(http://localhost:8040/actuator/hystrix.stream),应该是可以的:

那同理,我们用之前搭建的Hystrix Dashboard服务,可以对Zuul的Hystrix端点进行可视化查看,我们启动之前编写的Hystrix Dashboard服务,并查看Zuul的Hystrix端点:


在上面的监控图中,之前我们解释过各种参数的意义(详见之前的博文:https://blog.csdn.net/acmman/article/details/102314768),我们可以看到这里Circuit的状态为“Closed”,即当前断路器的状态为关闭状态,也就是说没有请求是异常触发了断路器的。
我们此时将User微服务停掉,然后不停的刷新user的findById服务,我们可以看到断路器就被打开了:


我们可以看到当被代理的服务为异常的情况时,此时反馈的信息为 “SHORTCIRCUIT”(翻译为“短路”),很不友好。此时我们想要像之前直接使用Hystrix一样,为代理的服务也设置异常回退的Fallback方法,怎么来实现呢?这就用到了Zuul的回退机制。

二、Zuul的退回机制

我们来为上面代理的User的服务设置异常回退的Fallback方法。
首先我们在Zuul的工程下,创建Zuul的全局回退的provider类“MyFallbackProvider”,该类继承了FallbackProvider父类:

package com.microserver.cloud;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

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 com.netflix.hystrix.exception.HystrixTimeoutException;

public class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return "microserver-provider-user";
    }

    @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".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

先不具体解释这段代码,我们先来看一下FallbackProvider父类的源码(这里我将源码中的英文注释翻译了一下):

package org.springframework.cloud.netflix.zuul.filters.route;
import org.springframework.http.client.ClientHttpResponse;

/**
 * 当路由上发生故障时提供回退。
 *
 * @author Ryan Baxter
 * @author Dominik Mostek
 */
public interface FallbackProvider {
    /**
     * 这里返回的是回退将用到的路由。
     * @return The route the fallback will be used for.
     */
    public String getRoute();

    /**
     * 根据执行失败的原因提供回退响应
     *
     * @param route The route the fallback is for
     * @param cause cause of the main method failure, may be <code>null</code>
     * @return the fallback response
     */
    ClientHttpResponse fallbackResponse(String route, Throwable cause);
}

我们通过源码的注释可以知晓,继承FallbackProvider父类,将实现“getRoute”以及“fallbackResponse”方法。其中“getRoute”方法返回一个回退时需要用到的路由,也就是我们代理的服务的路由名称。而“fallbackResponse”方法就是我们需要按照实际需要,给不同的失败原因,返回不同的响应。

那么回到我们前面的代码,该代码的“getRoute”方法,提供的路由为“microserver-provider-user”,即我们microserver-provider-user微服务的路由。如果要为所有路由提供默认回退,可以创建FallbackProvider类型的bean并使getRoute方法返回*或null。
然后fallbackResponse方法中,我们分析了断路器异常类型中的“HttpStatus.GATEWAY_TIMEOUT”以及“HttpStatus.INTERNAL_SERVER_ERROR”异常类型,然后分类通过response方法,返回一个ClientHttpResponse对象,该对象中会返回这个请求的回退响应信息,分别是响应状态对象“StatusCode”、状态码“RawStatusCode”、异常体信息“Body”以及响应头“Headers”,这里我们响应的ContentType类型为Json类型(MediaType.APPLICATION_JSON)。然后我们在启动配置类中注入这个Bean:

package com.microserver.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableZuulProxy
public class MircoserverGateWayZuulApplication {
    @Bean
    public RiskCheckFilter riskCheckFilter() {
        return new RiskCheckFilter();
    }
    
    @Bean
    public MyFallbackProvider myFallbackProvider() {
        return new MyFallbackProvider();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(MircoserverGateWayZuulApplication.class, args);
    }
}

我们重启一下Zuul,在User停机的状态下重新访问User的findById服务:

可以看到,现在不直接响应异常页面的,响应的信息为“fallback”,即之前我们编写的getBody()方法的返回值:

@Override
public InputStream getBody() throws IOException {
    return new ByteArrayInputStream("fallback".getBytes());
}

我们在MyFallbackProvider的fallbackResponse方法中打一个断点,用Debug的方式重新运行Zuul服务,然后访问User的findById服务,看看接收的异常是什么类型的:

我们可以看到,当前接收到的cause不是HystrixTimeoutException类型,即不是访问超时,那么就可以判断我们代理的微服务是有问题的,即可以反馈服务异常(这里的INTERNAL_SERVER_ERROR的响应码为500,响应信息为"Internal Server Error",详细类型和响应码,详见HttpStatus枚举类源码)。实际上通过“Load balancer does not have available server for client: microserver-provider-user”异常信息我们也可以得知访问的微服务已经没有可用的负载均衡器了,说明user服务已经挂了。

我们把上面的response方法修改一下,让它显示的异常信息更详细一点:

@Override
public InputStream getBody() throws IOException {
    return new ByteArrayInputStream(("fallback( code:" + Integer.toString(this.getRawStatusCode()) + "  message:" + this.getStatusText()+")").getBytes());
}

这里我们将异常码、异常信息全部显示出来,然后重启Zuul,重新访问User的findById服务:

我们可以看到异常响应信息修改成功。

三、各路FallBack回顾

既然讲了Zuul的回退机制,不得不提一下之前的几个组件使用Hystrix的回退机制。
1、Ribbon
对于使用Ribbon组件的微服务,在使用Hystrix时,在需要设置退回响应的服务方法上添加@HystrixCommand注解,然后指定fallbackMethod参数指向的方法名即可:

@Component
public class StoreIntegration {

    @HystrixCommand(fallbackMethod = "defaultStores")
    public Object getStores(Map<String, Object> parameters) {
        //do stuff that might fail
    }

    public Object defaultStores(Map<String, Object> parameters) {
        return /* something useful */;
    }
}

2、Feign
对于使用Feign组件进行声明式服务调用的服务,我们在相应的FeignClient接口的@FeignClient注解的fallback参数中指定其回退类(该退回类是该接口的实现类)即可:

@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

static class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

亦或是指定fallbackFactory参数,实现一个回退方法工厂类(工厂类的泛型是FeignClient接口):

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
    @Override
    public HystrixClient create(Throwable cause) {
        return new HystrixClient() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}

四、总结

这里我们总结一下Zuul的回退机制。实现Zuul的回退,有以下四步:
1、创建一个继承FallbackProvider回退提供器的父类的子类
2、实现FallbackProvider子类的“getRoute”方法,返回值即是代理的服务的路由地址(默认为该微服务在Eureka中的应用名)。
3、实现FallbackProvider子类的“fallbackResponse”方法,根据不同的异常类型,返回不同的异常信息。
4、将该FallbackProvider子类的Bean注入到启动或配置类中。


参考:《51CTO学院Spring Cloud高级视频》
https://blog.csdn.net/MobiusStrip/article/details/84099725

转载请注明出处:https://blog.csdn.net/acmman/article/details/103950792

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光仔December

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值