springCloud(11):使用Hystrix实现微服务的容错处理-简介与实现

一、问题引入

如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不作任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统的崩溃。

1.1、雪崩效应

微服务架构的应用系统通常包含多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。任何微服务都并非100%可用,网络往往也很脆弱,因此难免有些请求会失败。

我们通常把“基础服务故障”导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。

1.2、如何容错

要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需要实现以下两点:

a、为网络请求设置超时:为每个网络请求设置超时,让资源尽快释放。

b、使用断路器模式

我们可通过以上两点机制保护应用,从而防止雪崩效应并提升应用的可用性。

1.3、断路器模式简介

断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。

断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对改服务的调用快速失败,即不再请求所依赖的服务。这样应用程序就无须再浪费CPU时间去等待长时间的超时。

断路器也可自动诊断依赖的服务是否已经恢复正常,如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务,使用这种方式,就可以实现微服务的“自我修复”。

当依赖的服务不正常时打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。

断路器状态转换图,如下:

说明:

1、正常情况下,断路器关闭,可正常请求依赖的服务。

2、当一段时间内,请求失败率达到一定阔值(如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。

3、断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够成功调用,则关闭断路器;否则继续保持打开状态。

二、Hystrix简介

hystrix是一个实现了超时机制和断路器模式的工具类库。

2.1、简介

hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务、或者第三方库,防止级联失败,从而提升系统的可用性与容错性。

hystrix主要通过以下几点实现容错和延迟:

包裹请求

使用hystrixCommand(或hystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。

跳闸机制

当某服务的错误率超过一定阔值时,hystrix可以自动或手动跳闸,停止请求该服务一段时间。

资料隔离

hystrix为每个依赖都维护了一个小型的线程池(或信号量)。如果该线程池已满,发往该依赖的请求就会被立即拒绝,而不是排队等待,从而加速失败判定。

监控

hystrix可以近乎实时的监控运行指标和配置的变化,例如成功、失败、超时以及被拒绝的请求等

回退机制

当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。

自我修复

断路器打开一段时间后,会自动进入“半开”状态。

三、使用hystrix实现容错

3.1、通用方式整合Hystris

复制工程spring-ribbon-eureka-client,改名为spring-hystrix-consumer

3.1.1、添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

3.1.2、在启动类上加上注解@EnableHystrix

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
public class SpringHystrixConsumerApplication {
    @Bean
  @LoadBalanced
  public RestTemplate restTemplate(){
      return new RestTemplate();
  }
  public static void main(String[] args) {
      SpringApplication.run(SpringHystrixConsumerApplication.class, args);
  }
}

3.1.3、修改UserController.java

package com.example.demo.controller;

import com.example.demo.pojo.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * 用户controller
 *
 * @Author: 我爱大金子
 * @Description: 用户controller
 * @Date: Create in 11:07 2017/7/10
 */
@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @HystrixCommand(fallbackMethod = "findByIdFallback")    // 指定回退方法findByIdFallback
    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id) throws Exception {
        ServiceInstance serviceInstance = this.loadBalancerClient.choose("spring-ribbon-eureka-client2");
        // 打印当前选择的是哪个节点
        System.out.println("serviceId : " + serviceInstance.getServiceId());
        System.out.println("hoost : " + serviceInstance.getHost());
        System.out.println("port : " + serviceInstance.getPort());
        System.out.println("============================================================");

        if (null == id) {
            return null;
        }
        return this.restTemplate.getForObject(" + id, User.class);
    }

    /**回退方法*/
  public User findByIdFallback(Long id) {
      User user = new User();
    user.setId(-1L);
    user.setName("默认用户");
    return user;
  }

  @GetMapping("/log-instance")
  public void serviceInstance() throws Exception {
        ServiceInstance serviceInstance = this.loadBalancerClient.choose("spring-ribbon-eureka-client2");
    // 打印当前选择的是哪个节点
    System.out.println("serviceId : " + serviceInstance.getServiceId());
    System.out.println("hoost : " + serviceInstance.getHost());
    System.out.println("port : " + serviceInstance.getPort());
    System.out.println("============================================================");
  }
}

说明:为findById方法编写了一个回退方法findByIdFallback,该方法与findById方法具有相同的参数与返回值类型。

3.1.4、效果

访问:http://localhost:8086/user/1

关闭spring-ribbon-eureka-client2服务,再次访问

3.1.5、@HystrixCommand注解说明

在findById方法上,使用注解@HystrixCommand的fallbackMethod属性,指定回退方法是findByIdFallback。注解@HystrixCommand由名为javanica的Hystrix contrib库提供。javanica是一个Hystrix的子项目,用于简化Hystrix的使用。Spring cloud自动将Spring bean与该注解封装在一个连接到Hystrix断路器的代理中。

@HystrixCommand的配置非常灵活,可使用注解@HystrixCommand的commandProperties属性来配置@HystrixProperty。如:

@HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value="5000"),      // 超时时间,默认1000ms
  @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value="10000")           // 设置关于HystrixCommand执行需要的统计信息,默认10000毫秒
} )    // 指定回退方法findByIdFallback
@GetMapping("/user1/{id}")
public User findById1(@PathVariable Long id) throws Exception {
    ServiceInstance serviceInstance = this.loadBalancerClient.choose("spring-ribbon-eureka-client2");
  // 打印当前选择的是哪个节点
  System.out.println("serviceId : " + serviceInstance.getServiceId());
  System.out.println("hoost : " + serviceInstance.getHost());
  System.out.println("port : " + serviceInstance.getPort());
  System.out.println("============================================================");

  if (null == id) {
      return null;
  }
  return this.restTemplate.getForObject(" + id, User.class);
}

Hystrix配置属性详情请查看Hystrix Wiki: https://github.com/Netflix/Hystrix/wiki/Configuration

@HystrixCommand注解详情请查看:https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica

疑问:

我们知道,请请求失败,被拒绝、超时或者断路器打开时,都会进入回退方法。但进入回退方法并不意味着断路器已经被打开。那么,如何才能明确了解短路器当前的状态呢?

3.2、Feign使用Hystrix

前面的都是使用注解@HystrixCommand的fallbackMethod属性实现回退的。然而,Feign是以接口的形式工作的,它没有方法体,显然前面的方式并不适用于Feign。那么feign如果整合Hystrix呢?

事实上,Spring Cloud默认已为Feign整合了Hystrix,只要Hystrix在项目的classpath中,Feign默认就会用断路器包裹所有的方法。

3.2.1、为feign添加回退

1、复制项目spring-feign-consumer,改名为spring-hystrix-feign。

2、修改Feign接口

package com.example.demo.feign;

import com.example.demo.pojo.User;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Feign接口
 *
 * @Author: 我爱大金子
 * @Description: Feign接口
 * @Date: Create in 10:14 2017/7/17
 */
@FeignClient(name = "spring-ribbon-eureka-client2", fallback = FeignClientFallback.class)
public interface UserFeignClient {
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  public User findById(@PathVariable("id") Long id) throws Exception;
}

/**
 * 回退类FeignClientFallback需要实现Feign接口
 * @Author: 我爱大金子
 * @Description: 描述
 * @Date: 17:40 2017/7/18
 */
@Component
class FeignClientFallback implements UserFeignClient {
    @Override
  public User findById(Long id) throws Exception {
      User user = new User();
    user.setId(-1L);
    user.setName("默认用户");
    return user;
  }
}

注意:在新版中feign默认没有开启Hystrix的功能,开启配置如下:

feign:
    hystrix:
        enabled: true     # 开启Feign的Hystrix的功能

测试:

1、启动spring-ribbon-eureka-client2服务,访问http://localhost:8086/user/1,结果正常,如下:

2、关闭spring-ribbon-eureka-client2服务,访问http://localhost:8086/user/1,结果如下:

3.2.2、通过FallbackFactory检查回退原因

很多场景下,需要了解回退的原因,此时可使用注解@FeignClient的fallbackFactory属性。

1、复制上面工程,改名为spring-hystrix-feign-fallback-factory

2、修改feign接口

package com.example.demo.feign;

import com.example.demo.pojo.User;
import feign.hystrix.FallbackFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * Feign接口
 *
 * @Author: 我爱大金子
 * @Description: Feign接口
 * @Date: Create in 10:14 2017/7/17
 */
@FeignClient(name = "spring-ribbon-eureka-client2", fallbackFactory = FeignClientFallbackFactory.class)
public interface UserFeignClient {
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
  public User findById(@PathVariable("id") Long id) throws Exception;
}

/**
 * 回退类FeignClientFallback需要实现Feign接口
 * @Author: 我爱大金子
 * @Description: 描述
 * @Date: 17:40 2017/7/18
 */
@Component
class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
    private static final Logger log = LoggerFactory.getLogger(FeignClientFallbackFactory.class);
  @Override
  public UserFeignClient create(Throwable throwable) {
      return new UserFeignClient(){
            @Override
      public User findById(Long id) throws Exception {
          FeignClientFallbackFactory.log.info("fallback : ", throwable);
        User user = new User();
        user.setId(-1L);
        user.setName("默认用户");
        return user;
      }
    };
  }
}

3、测试

3.2.3、为Feign禁用Hystrix

在Spring cloud中,只要Hystrix在项目中的classpath中,Feign就会使用断路器包裹Feign客户端的所有方法。这样虽然方便,但有些场景下并不需要该功能。

为指定的Feign客户端禁用Hystrix

借助Feign的自定义配置,可轻松为指定名称的Feign客户端禁用Hystrix。如:

@Configuration
@ExcludeFromComponentScan
public class FeignConfiguration {
    @Bean
  @Scope("prototype")
  public Feign.Builder feignBuilder() {
      return Feign.builder();
  }
}

想要禁用Hystrix的@FeignClient引用改配置即可,如:

@FeignClient(name = "spring-ribbon-eureka-client2", configuration = FeignConfiguration.class)
public interface UserFeignClient {
    // ...
}

全局禁用Hystrix

只须在application.yml中配置feign.hystrix.enabled=false即可。

四、深入理解Hystrix

4.1、Hystrix断路器的状态监控

记得我们以前为项目引入过Actuator:

<dependency>
    <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

断路器的状态也暴露在Actuator提供的/health端点中,这样就可以直观的了解断路器的状态

实验:

1、启动spring-ribbon-eureka-client2服务,访问http://localhost:8086/user/1,可以正常获取结果

2、访问http://localhost:8086/manage/health,结果如下:

可以看到此时,Hystrix的状态是UP,也就是一切正常,此时断路器时关闭的。

3、关闭spring-ribbon-eureka-client2服务,访问http://localhost:8086/user/1,可以获取如下结果:

4、访问http://localhost:8086/manage/health,结果如下:

我们发现,尽管执行了回退逻辑,返回了默认用户,但此时Hystrix的状态依然是UP,这是因为我们的失败率还没有达到阔值(默认是5秒20次失败)。再次强调----执行回退逻辑并不代表断路器已经打开。请求失败、超时、被拒绝以及断路器打开时都会执行回退逻辑。

5、持续快速的访问http://localhost:8086/user/1,知道http://localhost:8086/manage/health的结果如下:

可以看到hystrix的状态是CIRCUIT_OPEN,说明短路器已经打开

4.2、Hystrix线程隔离策略与传播上下文

hystrix的隔离策略有两种:线程隔离和信号量隔离

线程隔离(THREAD):使用该方式,HystrixCommand将会在单独的线程上执行,并发请求受线程池中的线程数量的限制。

信号量隔离(SEMAPHORE):使用该方式,HystrixCommand将会在调用线程上执行,开销相对较小,并发请求受信号量个数的限制。

hystrix中默认并且推荐使用线程隔离,因为这种方式有一个除网络超时以外的额外保护层。

一般来说,只有当调用负载非常高时(如:每个实例每秒调用数百次)才需要使用信号量隔离,因为这种场景下使用线程隔离开销会比较大。信号量隔离一般仅适用于非网络调用的隔离。

可使用execution.isolation.strategy属性指定隔离策略。

设置信号量隔离策略:

@HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = {
    @HystrixProperty(name = "execution.isolation.strategy", value="SEMAPHORE"),      // 设置信号量隔离
})
@GetMapping("/user2/{id}")
public User findById2(@PathVariable Long id) throws Exception {
    // ...
}

总结:

1、hystrix的隔离策略有THREAD、SEMAPHORE两种,默认使用THREAD。

2、正常情况下,保持默认即可。

3、如果发生找不到上下文的运行时异常,可考虑将隔离级别设置为SEMAPHORE

时间: 2024-10-11 23:11:45

springCloud(11):使用Hystrix实现微服务的容错处理-简介与实现的相关文章

springCloud(12):使用Hystrix实现微服务的容错处理-Hystrix的监控

一.简介 Hystrix提供了几乎实时的监控.HystrixCommand和HystrixObserv-ableCommand在执行时,会生成执行结果和运行指标,比如每秒执行的请求数.成功数等,这些监控数据对分析应用系统的状态很有用. 使用Hystrix的模块hystrix-metrics-event-stream,就可将这些监控的指标信息以text/event-stream的格式暴露给外部系统.spring-cloud-starter-hystrix已包含该模块,在此基础上,只须为项目添加sp

springcloud-hystrix断路器对微服务的容错处理

使用Hystrix实现微服务的容错处理 1.实现容错的手段 如果服务提供者响应的速度特别慢,那么消费者对提供者的请求就会强制等待,直到提供者响应或者超时.在高负载的情况下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统的崩溃.例如曾经发生的一个案例,某个电子商务网站在某个星期五发生过载,过多的并法请求,导致用户支付请求延迟很久没有响应,在等待很长时间后最终失败,支付失败又导致用户重新刷新页面再次尝试支付,进一步增加了服务器的负载,最终整个系统崩溃了. 1.1雪崩效应 我们常

[Spring cloud 一步步实现广告系统] 11. 使用Feign实现微服务调用

上一节我们使用了Ribbon(基于Http/Tcp)进行微服务的调用,Ribbon的调用比较简单,通过Ribbon组件对请求的服务进行拦截,通过Eureka Server 获取到服务实例的IP:Port,然后再去调用API.本节课我们使用更简单的方式来实现,使用声明式的Web服务客户端Feign,我们只需要使用Feign来声明接口,利用注解来进行配置就可以使用了,是不是很简单?实际工作中,我们也只会用到Feign来进行服务之间的调用(大多数).接下来,我们来实例操作一把. 为了代码的重用性,我们

springcloud vue.js 前后分离 微服务 分布式 activiti工作流 集成代码生成器 shiro权限

1.代码生成器: [正反双向](单表.主表.明细表.树形表,快速开发利器)freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本.处理类.service等完整模块2.多数据源:(支持同时连接无数个数据库,可以不同的模块连接不同数的据库)支持N个数据源3.阿里数据库连接池druid,安全权限框架 shiro(菜单权限和按钮权限), 缓存框架 ehcache4.代码编辑器,在线模版编辑,仿开发工具编辑器5.调用摄像头拍照 自定义裁剪编辑头像,头像图片色度调节6.we

springCloud(16):使用Zuul构建微服务网关-容错回退与高可用

一.容错与回退 1.1.容错 在Spring Cloud中,Zuul默认已经整合了Hystrix. 测试: a.依次启动eureka-server(4010).provide-user(4011).hystrix-consumer-movie(5012).microservice-gateway-zuul(5016).hystrix-dashboard(5013) b.访问http://localhost:5016/hystrix-consumer-movie/user/1,可正常获取结果 c.

【SpringCloud】(1)---基于RestTemplate微服务项目案例

基于RestTemplate微服务项目 在写SpringCloud搭建微服务之前,我想先搭建一个不通过springcloud只通过SpringBoot和Mybatis进行模块之间额通讯.然后在此基础上再添加SpringCloud框架. 下面先对案例做个说明 该项目有一个maven父模块,其中里面有三个子模块:  serverspringcloud:整体父工程.    serverspringcloud-api:公共子模块,放公共实体对象.  serverspringcloud-provider-

springCloud(1):微服务简介

一.什么是微服务 微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API). 二.微服务架构特性 1.每个微服务可独立运行在自己的进程里 2.一系列独立运行的微服务共同构建起整个系统 3.每个服务为独立的业务开发,一个微服务只关注某个特定的功能,如:订单管理 4.微服务之间通过一些轻量级的通信机制进行通信,例如:通过RESTful API进行调用 5.可以使用不同的语言与数据存储技术 6.全自动的部署机制

springCloud(3):微服务的注册与发现(Eureka)

一.简介 服务消费者需要一个强大的服务发现机制,服务消费者使用这种机制获取服务提供者的网络信息.即使服务提供者的信息发生变化,服务消费者也无须修改配置. 服务提供者.服务消费者.服务发现组件三者之间的关系大致如下: 1.各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息. 2.服务消费者可以从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口. 3.各个微服务与服务发现组件使用一定机制(如:心跳)通信,服务发现组件如长时间无法与某微服务

微服务(Microservices )简介

概念 微服务架构风格是一种将单个应用程序作为一套小型服务开发的方法,每种应用程序都在自己的进程中运行, 并与轻量级机制(通常是HTTP资源API)进行通信. 这些服务是围绕业务功能构建的,可以通过全自动部署机制独立部署. 这些服务的集中管理最少, 可以使用不同的编程语言编写, 并使用不同的数据存储技术. 特点 1 独立部署,灵活扩展(组件以服务形式来提供) 2 强化终端及弱化通道: 微服务的应用致力松耦合和高内聚,它们更喜欢简单的REST 风格,而不是复杂的协议(如WS或者BPEL或者集中式框架