【夯实Spring Cloud】Spring Cloud中使用Hystrix实现断路器原理详解(上)

版权声明:尊重博主原创文章,转载请注明出处 https://blog.csdn.net/eson_15/article/details/86628622

本文属于【夯实Spring Cloud】系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习Spring Cloud技术,希望能给读者带来一些干货。系列目录如下:

【夯实Spring Cloud】Dubbo沉睡5年,Spring Cloud开始崛起!
【夯实Spring Cloud】Spring Cloud中基于maven的分布式项目框架的搭建
【夯实Spring Cloud】Spring Cloud中的Eureka服务注册与发现详解
【夯实Spring Cloud】Spring Cloud中如何完善Eureka中的服务信息
【夯实Spring Cloud】Spring Cloud中使用Eureka集群搭建高可用服务注册中心
【夯实Spring Cloud】Spring Cloud中的Eureka和Zookeeper的区别在哪?
【夯实Spring Cloud】Spring Cloud中使用Ribbon实现负载均衡详解(上)
【夯实Spring Cloud】Spring Cloud中使用Ribbon实现负载均衡详解(下)
【夯实Spring Cloud】Spring Cloud中自定义Ribbon负载均衡策略
【夯实Spring Cloud】Spring Cloud中使用Feign实现负载均衡详
【夯实Srping Cloud】Spring Cloud中使用Hystrix实现断路器原理详解(上)
【夯实Srping Cloud】Spring Cloud中使用Hystrix实现断路器原理详解(下)
【夯实Spring Cloud】Spring Cloud中使用Zuul实现路由网关详解
【夯实Spring Cloud】Spring Cloud分布式配置中心详解
【夯实Spring Cloud】未完待续


前面几篇文章分析了 Spring Cloud 中的 Ribbon 和 Feign 实现负载均衡机制。但是有个问题需要注意下:

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又在调用其他的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,那么对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,这就是所谓的“雪崩效应”。

1. Hystrix 登场

出现这种“雪崩效应”肯定是可怕的,在分布式系统中,我们无法保证某个服务一定不出问题,Hystrix 可以解决。

Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多服务无法避免会调用失败,比如超时、异常等等,Hystrix能够保证在一个服务出现问题的情况下,不会导致整体服务的失败,避免级联故障,以提高分布式系统的弹性。

所以叫“断路器”。“断路器”是一种开关装置,就好比我们家里的熔断保险丝,当出现突发情况,会自动跳闸,避免整个电路烧坏。那么当某个服务发生故障时,通过 Hystrix,会向调用方返回一个符合预期的、可处理的默认响应(也称备选响应,即fallBack),而不是长时间的等待或者直接返回一个异常信息。这样就能保证服务调用方可以顺利的处理逻辑,而不是那种漫长的等待或者其他故障。

这就叫“服务熔断”,就跟熔断保险丝一个道理。

2. 服务熔断和服务降级

服务熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长,就会进行服务的降级,快速熔断该节点微服务的调用,返回默认的响应信息。当检测到该节点微服务调用响应正常后即可恢复。

上面提到服务的降级,什么意思呢?我打个比方:比如你去银行办理业务,本来有四个窗口都可以办理,现在3号窗口和4号窗口的办理人员有事要离开,那么自然地,用户就会跑去1号窗口或者2号窗口办理,所以1号和2号窗口就会承担更多的压力。

3号窗口和4号窗口的人有事走了,不能让人还在这排队等着吧,否则就出现了上文说的雪崩了,所以会挂一个牌子:暂停服务。这个牌子好比上文提到的熔断,然后返回一个默认的信息,让用户知道。等3号和4号窗口的人回来了,就会把这个牌子拿走,这两个窗口又可以继续回复服务了。

服务降级是在客户端完成的,不是服务端,与服务端是没有关系的。就像银行某个窗口挂了“暂停服务”,那客户会自然去别的窗口。

3. Hystrix 的使用

上面介绍了 Hystrix 的基本原理,接下来我们来落地到代码实现。新建一个项目工程:microservice-order-provider01-hystrix。然后将前面的miroservice-order-provider01的代码拷贝过来,做如下修改:

3.1 依赖导入

<dependencies>
    <!-- hystrix -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
<!--eureka-client客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

3.2 启动类注解

在启动类中,需要添加 @EnableCircuitBreaker 注解

@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.itcodai.springcloud.dao")
@EnableCircuitBreaker
public class OrderProvider01Hystrix {

    public static void main(String[] args) {
        SpringApplication.run(OrderProvider01Hystrix.class, args);
    }
}

3.3 对接口的改动

我们一起来看下 Controller 层的接口:

/**
 * 订单服务
 * @author shengwu ni
 */
@RestController
@RequestMapping("/provider/order")
public class OrderProviderController {

    @Resource
    private OrderService orderService;

    private static final Logger LOGGER = LoggerFactory.getLogger(OrderProviderController.class);

    /**
     * HystrixCommond注解中的fallbackMethod指示的是:当该方法出异常时,调用processGetOrderHystrix方法
     * @param id id
     * @return 订单信息
     */
    @GetMapping("/get/{id}")
    @HystrixCommand(fallbackMethod = "processGetOrderHystrix")
    public TOrder getOrder(@PathVariable Long id) {
        TOrder order = orderService.findById(id);
        if (order == null) {
            throw new RuntimeException("数据库没有对应的信息");
        }
        return order;
    }

    /**
     * 上面getOrder()方法出异常后的熔断处理方法
     * @param id id
     * @return 订单信息
     */
    public TOrder processGetOrderHystrix(@PathVariable Long id) {
        return new TOrder().setId(id)
                .setName("未找到该ID的结果")
                .setPrice(0d)
                .setDbSource("No this datasource");
    }
}

我来分析一下代码:OrderService 是上一节的 Feign 接口,我们使用 Feign 来接口式调用。我们看到,在 getOrder(id) 方法上添加了 @HystrixCommand(fallbackMethod = "processGetOrderHystrix") 注解,我简单解释一下:

@HystrixCommand 表示该接口开启 hystrix 熔断机制,如果出现问题,就去调用 fallbackMethod 属性指定的 processGetOrderHystrix 方法,那么往下看,就能看到 processGetOrderHystrix 方法,我们返回了和上面接口一样的数据结构,只不过都是我们自己搞的默认值而已。

getOrder(id) 这个接口中,当查不到订单信息,我故意手动抛出个异常方便我测试,我测试的时候把id搞大点,查不到即可。

4. 测试一下

启动 eureka 集群,启动这个带有熔断机制的订单提供服务:microservice-order-provider01-hystrix,再启动上一节的 Feign 客户端,在浏览器中输入:
http://localhost:9001/consumer/order/get/100,故意将id设为100,就会看到给我返回如下信息:

{"id":100,"name":"未找到该ID的结果","price":0.0,"dbSource":"No this datasource"}

这就说明 hystrix 已经做了熔断处理,请求没有任何问题。

5. 思考

上面介绍了 hystrix 的服务熔断和降级处理,但是有没有发现一个问题,这个 @HystrixCommand 注解是加在 Controller 层的接口方法上的,这会导致两个问题:

第一:如果接口方法很多,那么我是不是要在每个方法上都得加上该注解,而且,针对每个方法,我都要指定一个处理函数,这样会导致 Controller 变得越来越臃肿。

第二:这也不符合设计规范,理论上来说,Controller 层就是 Controller 层,我只管写接口即可。就像上一节介绍的 Feign,也是面向接口的,做均衡处理,我自己定义一个接口专门用来做均衡处理,在 Controller 层将该接口注入即可。那么 hystrix 是否也可以有类似的处理呢?

答案是肯定的,这跟面向切面编程一个道理,Cotroller 你只管处理接口逻辑,当出了问题,OK,交给我 hystrix ,我 hystrix 不在你 Controller 这捣蛋,我去其他地方呆着,你有问题了,我再来处理。这才是正确的、合理的设计方式。

5.1 定义 Hystrix 处理类

所以我们完全不用像上文那样新建一个带 hystrix 的订单提供服务:microservice-order-provider01-hystrix。我们新建一个 hystrix 处理类:OrderClientServiceFallbackFactory,要实现
FallbackFactory<OrderClientService> 接口,其中 OrderClientService 就是前面定义的 Feign 接口。

也就是说,把 hystrix 和 feign 绑起来,因为都是客户端的东东。我通过 feign 去调用服务的时候,如果出问题了,就来执行我自定义的 hystrix 处理类中的方法,返回默认数据。代码如下:

/**
 * 统一处理熔断
 * OrderClientService是Feign接口,所有访问都会走feign接口
 * @author shengwu ni
 */
@Component
public class OrderClientServiceFallbackFactory implements FallbackFactory<OrderClientService> {

    @Override
    public OrderClientService create(Throwable throwable) {
        return new OrderClientService() {

            /**
             * 当订单服务的getOrder()方法出异常后的熔断处理方法
             * @param id id
             * @return 返回信息
             */
            @Override
            public TOrder getOrder(Long id) {
                return new TOrder().setId(id)
                        .setName("未找到该ID的结果")
                        .setPrice(0d)
                        .setDbSource("No this datasource");
            }

            @Override
            public List<TOrder> getAll() {
                return null;
            }
        };
    }
}

我来分析一下代码,实现了 FallbackFactory<OrderClientService> 接口后,需要重写 create 方法,还是返回 OrderClientService 接口对象,只不过对这个 feign 客户端做了默认处理。

5.2 给 Feign 指定 hystrix

OK,现在 hystrix 是绑定了 Feign 接口了,但是 Feign 接口中的某个方法如果出问题了,它怎么知道找谁去做熔断呢?所以在 Feign 接口也需要绑定一下我们定义的 hystrix 处理类:

/**
 * feign客户端
 * @author shengwu ni
 */
//@FeignClient(value = "MICROSERVICE-ORDER")
@FeignClient(value = "MICROSERVICE-ORDER", fallbackFactory = OrderClientServiceFallbackFactory.class)
public interface OrderClientService {

    @GetMapping("/provider/order/get/{id}")
    TOrder getOrder(@PathVariable(value = "id") Long id);

    @GetMapping("/provider/order/get/list")
    List<TOrder> getAll();
}

我把之前的注释掉了,新添加了个 fallbackFactory 属性,指定了自定义的 hystrix 处理类。这样的话,Controller 中的所有方法都可以在 hystrix 里有个默认实现了。

同时,别忘了在 application.yml 中开启熔断:

# 开启熔断
feign:
  hystrix:
    enabled: true

OK,重新测一下,启动 eureka 集群,启动之前写好的未加 hystrix 的订单提供服务,三个当中随便起一个即可。再启动带有 hystrix 的 Feign 客户端,再测试一下上面的 url 即可。


源码下载地址:https://gitee.com/eson15/springcloud_study
更多优质文章请关注我的微信公众号【程序员私房菜】,回复“资源”和“架构”可以领取优质的视频学习资源。
程序员私房菜

猜你喜欢

转载自blog.csdn.net/eson_15/article/details/86628622