SpringCloud笔记04-Hystix

传送门
SpringCloud笔记01-简介
SpringCloud笔记02-Eureka
SpringCloud笔记03-Ribbon
SpringCloud笔记05-Feign
SpringCloud笔记06-Zuul

简介:

Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

Hystix依赖:

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

启动类注解:@EnableHystrix

雪崩效应常见场景

  • 硬件故障:如服务器宕机,机房断电等。
  • 流量激增:如异常流量,重试加大流量等
  • 缓存穿透:一般发生在应用重启,所有缓存失效时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端服务,造成服务提供者超负荷运行,引起服务不可用。
  • 程序BUG:如程序逻辑导致内存泄漏,JVM长时间FullGC等。
  • 同步等待:服务间采用同步调用模式,同步等待造成的资源耗尽。

雪崩效应应对策略

  • 硬件故障:多机房容灾、异地多活等。
  • 流量激增:服务自动扩容、流量控制(限流、关闭重试)等。
  • 缓存穿透:缓存预加载、缓存异步加载等。
  • 程序BUG:修改程序bug、及时释放资源等。
  • 同步等待:资源隔离、MQ解耦、不可用服务调用快速失败等。资源隔离通常指不同服务调用采用不同的线程池;不可用服务调用快速失败一般通过熔断器模式结合超时机制实现。

熔断器的工作机制:

熔断机制的原理很简单,像家里的电路熔断器,如果电路发生短路能立刻熔断电路,避免发生灾难。在分布式系统中应用这一模式之后,服务调用方可以自己进行判断某些服务反应慢或者存在大量超时的情况时,能够主动熔断,防止整个系统被拖垮。

不同于电路熔断只能断不能自动重连, Hystrix可以实现弹性容错,当情况好转之后,可以自动重连。

通过断路的方式,可以将后续请求直接拒绝掉,一段时间之后允许部分请求通过,如果调用成功则回到电路闭合状态,否则继续断开。

改造消费者

@Component
public class UserDao {

    @Autowired
    private RestTemplate restTemplate;

    private static final Logger logger = LoggerFactory.getLogger(UserDao.class);

    @HystrixCommand(fallbackMethod = "queryUserByIdFallback")
    public User queryUserById(Long id){
        long begin = System.currentTimeMillis();
        String url = "http://user-service/user/" + id;
        User user = this.restTemplate.getForObject(url, User.class);
        long end = System.currentTimeMillis();
        // 记录访问用时:
        logger.info("访问用时:{}", end - begin);
        return user;
    }

    public User queryUserByIdFallback(Long id){
        User user = new User();
        user.setId(id);
        user.setName("用户信息查询出现异常!");
        return user;
    }
}

@HystrixCommand(fallbackMethod="queryUserByIdFallback"):声明一个失败回滚处理函数queryUserByIdFallback,当queryUserById执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。

注: Ribbon的超时时间一定要小于Hystix的超时时间。 否则ribbon重试机制没有被触发,而是先触发了熔断。

可以通过
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置Hystrix超时时间。

hystrix:
  command:
  	default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 6000 # 设置hystrix的超时时间为6000ms

Hystrix整个工作流

  1. 构造一个HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数;
  2. 执行命令,Hystrix提供了4种执行命令的方法,后面详述;
  3. 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动;
  4. 判断熔断器是否打开,如果打开,跳到第8步;
  5. 判断线程池/队列/信号量是否已满,已满则跳到第8步;
  6. 执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
  7. 统计熔断器监控指标;
  8. 走Fallback备用逻辑
  9. 返回请求响应

执行命令的几种方法

Hystrix提供了4种执行命令的方法,execute()和queue() 适用于HystrixCommand对象,而observe()和toObservable()适用于HystrixObservableCommand对象。

Hystrix容错

1. 资源隔离
资源隔离主要指对线程的隔离。Hystrix提供了两种线程隔离方式:线程池和信号量。

  • 线程隔离-线程池
    Hystrix通过命令模式对发送请求的对象和执行请求的对象进行解耦,将不同类型的业务请求封装为对应的命令请求。

    通过将发送请求线程与执行请求的线程分离,可有效防止发生级联故障。当线程池或请求队列饱和时,Hystrix将拒绝服务,使得请求线程可以快速失败,从而避免依赖问题扩散。

    线程池隔离优点:

  • 保护应用程序以免受来自依赖故障的影响,指定依赖线程池饱和不会影响应用程序的其余部分。

  • 当引入新客户端lib时,即使发生问题,也是在本lib中,并不会影响到其他内容。

  • 当依赖从故障恢复正常时,应用程序会立即恢复正常的性能。

  • 当应用程序一些配置参数错误时,线程池的运行状况会很快检测到这一点(通过增加错误,延迟,超时,拒绝等),同时可以通过动态属性进行实时纠正错误的参数配置。

  • 如果服务的性能有变化,需要实时调整,比如增加或者减少超时时间,更改重试次数,可以通过线程池指标动态属性修改,而且不会影响到其他调用请求。

  • 除了隔离优势外,hystrix拥有专门的线程池可提供内置的并发功能,使得可以在同步调用之上构建异步门面(外观模式),为异步编程提供了支持(Hystrix引入了Rxjava异步框架)。

    线程池隔离缺点:
    线程池的主要缺点是增加了计算开销。每个命令的执行都在单独的线程完成,增加了排队、调度和上下文切换的开销。因此,要使用Hystrix,就必须接受它带来的开销,以换取它所提供的好处。

  • 线程隔离-信号量
    上面提到了线程池隔离的缺点,当依赖延迟极低的服务时,线程池隔离技术引入的开销超过了它所带来的好处。这时候可以使用信号量隔离技术来代替,通过设置信号量来限制对任何给定依赖的并发调用量。

    使用线程池时,发送请求的线程和执行依赖服务的线程不是同一个,而使用信号量时,发送请求的线程和执行依赖服务的线程是同一个,都是发起请求的线程。

    由于Hystrix默认使用线程池做线程隔离,使用信号量隔离需要显示地将属性execution.isolation.strategy设置为ExecutionIsolationStrategy.SEMAPHORE,同时配置信号量个数,默认为10。客户端需向依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流程。

    信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩的目的。

  • 线程池和信号量都可以做线程隔离,但各有各的优缺点和支持的场景,对比如下:
    在这里插入图片描述线程池和信号量都支持熔断和限流。相比线程池,信号量不需要线程切换,因此避免了不必要的开销。但是信号量不支持异步,也不支持超时,也就是说当所请求的服务不可用时,信号量会控制超过限制的请求立即返回,但是已经持有信号量的线程只能等待服务响应或从超时中返回,即可能出现长时间等待。线程池模式下,当超过指定时间未响应的服务,Hystrix会通过响应中断的方式通知线程立即结束并返回。

2. 熔断

  1. 调用allowRequest()判断是否允许将请求提交到线程池
    -如果熔断器强制打开,circuitBreaker.forceOpen为true,不允许放行,返回。
    -如果熔断器强制关闭,circuitBreaker.forceClosed为true,允许放行。此外不必关注熔断器实际状态,也就是说熔断器仍然会维护统计数据和开关状态,只是不生效而已。
  2. 调用isOpen()判断熔断器开关是否打开
    -如果熔断器开关打开,进入第三步,否则继续;
    -如果一个周期内总的请求数小于circuitBreaker.requestVolumeThreshold的值,允许请求放行,否则继续;
    -如果一个周期内错误率小于circuitBreaker.errorThresholdPercentage的值,允许请求放行。否则,打开熔断器开关,进入第三步。
  3. 调用allowSingleTest()判断是否允许单个请求通行,检查依赖服务是否恢复
    -如果熔断器打开,且距离熔断器打开的时间或上一次试探请求放行的时间超过circuitBreaker.sleepWindowInMilliseconds的值时,熔断器器进入半开状态,允许放行一个试探请求;否则,不允许放行。

此外,为了提供决策依据,每个熔断器默认维护了10个bucket,每秒一个bucket,当新的bucket被创建时,最旧的bucket会被抛弃。其中每个blucket维护了请求成功、失败、超时、拒绝的计数器,Hystrix负责收集并统计这些计数器。

3. 回退降级
降级,通常指务高峰期,为了保证核心服务正常运行,需要停掉一些不太重要的业务,或者某些服务不可用时,执行备用逻辑从故障服务中快速失败或快速返回,以保障主体业务不受影响。Hystrix提供的降级主要是为了容错,保证当前服务不受依赖服务故障的影响,从而提高服务的健壮性。要支持回退或降级处理,可以重写HystrixCommand的getFallBack方法或HystrixObservableCommand的resumeWithFallback方法。

Hystrix在以下几种情况下会走降级逻辑:

执行construct()或run()抛出异常
熔断器打开导致命令短路
命令的线程池和队列或信号量的容量超额,命令被拒绝
命令执行超时

降级回退方式:

  • Fail Fast 快速失败
    快速失败是最普通的命令执行方法,命令没有重写降级逻辑。 如果命令执行发生任何类型的故障,它将直接抛出异常。
  • Fail Silent 无声失败
    指在降级方法中通过返回null,空Map,空List或其他类似的响应来完成。
  • Fallback: Static
    指在降级方法中返回静态默认值。 这不会导致服务以“无声失败”的方式被删除,而是导致默认行为发生。如:应用根据命令执行返回true / false执行相应逻辑,但命令执行失败,则默认为true
  • Fallback: Stubbed
    当命令返回一个包含多个字段的复合对象时,适合以Stubbed 的方式回退。
  • Fallback: Cache via Network
    有时,如果调用依赖服务失败,可以从缓存服务(如redis)中查询旧数据版本。由于又会发起远程调用,所以建议重新封装一个Command,使用不同的ThreadPoolKey,与主线程池进行隔离。

yml配置:

hystrix:
   command:
      StoreSubmission:
         execution.isolation.thread.timeoutInMilliseconds: 30000 # 执行的超时时间,单位为毫秒
         circuitBreaker:
            requestVolumeThreshold: 4 # 用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为20的时候,如果滚动时间窗(默认10秒)内仅收到19个请求,即使这19个请求都失败了,断路器也不会打开。
            sleepWindowInMilliseconds: 60000 # 用来设置当断路器打开之后的休眠时间窗。休眠时间窗结束之后,会将断路器设置为“半开”状态,尝试熔断的请求命令,如果依然失败时候就将断路器继续设置为“打开”状态,如果成功,就设置为“关闭”状态。
            errorThresholdPercentage:50 # 该属性用来设置断路器打开的错误百分比条件。默认值为50,表示在滚动时间窗中,在请求值超过requestVolumeThreshold阈值的前提下,如果错误请求数百分比超过50,就把断路器设置为“打开”状态,否则就设置为“关闭”状态
         metrics.rollingStats.timeInMilliseconds: 180000 # 用来设置百分位统计的滚动窗口的持续时间,单位为毫秒
   collapser:
      StoreSubmission:
         maxRequestsInBatch: 1 # 该参数用来设置一次请求合并批处理中允许的最大请求数
         timerDelayInMilliseconds:10 # 用来设置批处理过程中每个命令延迟的时间,单位为毫秒,默认值为10
         requestCache.enabled: FALSE # 设置批处理过程中是否开启请求缓存
   threadpool:
      StoreSubmission:
         coreSize: 10 # 该参数用来设置执行命令线程池的核心线程数,该值也是命令执行的最大并发量
         maximumSize: 10 # 该参数用来设置线程池的最大队列大小
         queueSizeRejectionThreshold: 5 # 该参数用来为队列设置拒绝阈值。通过该参数,即使队列没有达到最大值也能拒绝请求
         metrics:
            rollingStats:
               timeInMilliseconds: 180000 # 该参数用来设置滚动时间窗的长度,单位为毫秒
               numBuckets: 10 # 设置滚动时间窗划分成桶的数量
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页