快速入门 | 轻松掌握Hystrix实现资源隔离保护系统稳定

开发 前端
当有大量的请求调用时我们的所有线程都会被阻塞T的时间。本身Tomcat线程池的线程数量是有限的,这就会造成很多的客户端只能等待,尤其是越是后来的请求等待的时间会越长,同时由于这一个接口把所有的tomcat线程全部占用完,导致了其他的所有服务不可用全部处于等待状态,从而会拖垮整个tomcat,而这种现象我们称诶雪崩效应。

先看下如下图,两个服务之间的调用 A服务调用另外一个B服务。

图片图片

在这个图当中有个接口A需要调用另外一个服务的接口B。这里看似没有什么问题。

例如,本身A服务接口执行逻辑需要5ms执行完后再调用B服务接口的,调用B接口执行完成需要4s,比如下面的下定单扣库存的流程:

图片图片

这里整个接口调用链执行完成需要实际T=4s+5ms;

当有大量的请求调用时我们的所有线程都会被阻塞T的时间。本身Tomcat线程池的线程数量是有限的,这就会造成很多的客户端只能等待,尤其是越是后来的请求等待的时间会越长,同时由于这一个接口把所有的tomcat线程全部占用完,导致了其他的所有服务不可用全部处于等待状态,从而会拖垮整个tomcat,而这种现象我们称诶雪崩效应。

对于服务之间的调用我们也应该能够设置一个超时时间,不能让其一直等待下去。当超过了给定的时间后我们也能够给予用户一个响应,通过这种方式来避免这种级联的故障。如上所述,当整个A服务不可用的时候 这时候的B服务也就不可用了,这种现象被称为雪崩效应。

雪崩效应常见场景

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

雪崩效应应对策略

针对造成雪崩效应的不同场景,可以使用不同的应对策略,没有一种通用所有场景的策略,参考如下:

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

在程序中我们能通过Hystrix来实现资源的隔离,保护我们的服务,不至于导致整个tomcat服务不可用。

Hystrix是Netflix开源的一款针对分布式系统的延迟和容错库,目的是用来隔离分布式服务故障。它提供线程和信号量隔离,以减少不同服务之间资源竞争带来的相互影响;提供优雅降级机制;提供熔断机制使得服务可以快速失败,而不是一直阻塞等待服务响应,并能从中快速恢复。Hystrix通过这些机制来阻止级联失败并保证系统弹性、可用。通过下图来理解:

图片图片

也就是在调用B服务接口的时候我们放到另外的一个线程中去执行,防止出现上面说的问题。

接下来我们来通过程序代码来看看如何使用hystrix。

这里我们以调用订单服务为例

pom.xml加入依赖

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

方式1:

通过继承HystrixCommand

public class OrdersCommand extends HystrixCommand<Orders> {


  private RestTemplate restTemplate ;
  private Long id ;


  public OrdersCommand(RestTemplate restTemplate, Long id) {
    super(buildSetter()) ;
    this.restTemplate = restTemplate ;
    this.id = id ;
  }
  private static Setter buildSetter() {
    com.netflix.hystrix.HystrixThreadPoolProperties.Setter threadPoolProp = com.netflix.hystrix.HystrixThreadPoolProperties.Setter() ;
    threadPoolProp.withCoreSize(5)
      .withKeepAliveTimeMinutes(5)
      .withMaxQueueSize(Integer.MAX_VALUE)
      .withQueueSizeRejectionThreshold(1000) ;


    com.netflix.hystrix.HystrixCommandProperties.Setter commandProp = com.netflix.hystrix.HystrixCommandProperties.Setter() ;
    commandProp.withCircuitBreakerEnabled(true)
      .withExecutionTimeoutInMilliseconds(6000)
      .withRequestCacheEnabled(true)
      .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD);


    return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orders"))
           .andCommandKey(HystrixCommandKey.Factory.asKey("getOrder"))
           .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("order-pool"))
           .andThreadPoolPropertiesDefaults(threadPoolProp)
           .andCommandPropertiesDefaults(commandProp) ;
  }


  @Override
  protected Orders run() throws Exception {
    return restTemplate.getForObject("http://localhost:9810/orders/queryOrder/{1}", Orders.class, id);
  }


  @Override
  protected Orders getFallback() {
    return new Orders() ;
  }
  @Override
  protected String getCacheKey() {
    return "order-" + this.id ;
  }


}

这里我们实现了父类的getFallback方法

该方法为当服务调用失败或者超时会被调用。

这里通过buildSetter方法来构建hystrix相关的配置。说明:

threadPoolProp.withCoreSize(5)
      .withKeepAliveTimeMinutes(5)
      .withMaxQueueSize(Integer.MAX_VALUE)
      .withQueueSizeRejectionThreshold(1000) ;

以上是对线程池的配置。

其它设置:

RequestVolumeThreshold

HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(int)表示在滑动窗口中,至少有多少个请求,才可能触发断路。Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。

ErrorThresholdPercentage

HystrixCommandProperties.Setter().withCircuitBreakerErrorThresholdPercentage(int)表示异常比例达到多少,才会触发断路,默认值是 50(%)。如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。

SleepWindowInMilliseconds

HystrixCommandProperties.Setter().withCircuitBreakerSleepWindowInMilliseconds(int)断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后在 SleepWindowInMilliseconds 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。而在该参数时间过后,断路器会变为 half-open 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。

EnabledHystrixCommandProperties.Setter().withCircuitBreakerEnabled(boolean)控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 true。

ForceOpen

HystrixCommandProperties.Setter().withCircuitBreakerForceOpen(boolean)如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 false。

ForceClosedHystrixCommandProperties.Setter().withCircuitBreakerForceClosed(boolean)

如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 false。

参考:

图片图片

com.netflix.hystrix.HystrixCommandProperties.Setter commandProp = com.netflix.hystrix.HystrixCommandProperties.Setter() ;
    commandProp.withCircuitBreakerEnabled(true)
      .withExecutionTimeoutInMilliseconds(6000)
      .withRequestCacheEnabled(true)
      .withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD);

以上是对命令属性的配置。

withCircuitBreakerEnabled:控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 true。

withExecutionTimeoutInMilliseconds:执行超时时间的设置。如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。

withExecutionIsolationStrategy:执行隔离的策略,这里设置为线程。还可以设置为基于信号量的

ExecutionIsolationStrategy.SEMAPHORE:一般如果并发量比较大的情况下我们用信号量,性能要好。如果并发量大你还用线程池,那么你该创建多少的线程呢?而过多的线程带来了更多线程的切换而影响性能。

return Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orders"))
   .andCommandKey(HystrixCommandKey.Factory.asKey("getOrder"))
   .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("order-pool"))
   .andThreadPoolPropertiesDefaults(threadPoolProp)
   .andCommandPropertiesDefaults(commandProp) ;

withGroupKey:服务分组;比如这里调用订单系统就是一个服务分组。模块;

andCommandKey:服务标识;比如这里订单系统有一个获取订单信息服务。子模块;

andThreadPoolKey:线程池名称;

andThreadPoolPropertiesDefaults:线程池配置;

andCommandPropertiesDefaults:命令属性配置;

示例:

@GetMapping("/custom/{id}")
public Object custom(@PathVariable Long id) {
  HystrixRequestContext ctx = HystrixRequestContext.initializeContext() ;
  try {
    OrdersCommand command = new OrdersCommand(restTemplate, id) ;
    System.out.println(Thread.currentThread().getName() + ": " + System.currentTimeMillis()) ;
    Orders res = command.execute() ;
  } finally {
    ctx.shutdown() ;
  }
  return null ;
}

注意:HystrixRequestContext ctx =HystrixRequestContext.initializeContext() ;这行代码必须调用。

方式2:

通过注解的方式。

@Service
public class RemoteHystrixService {
  @Resource
  private RestTemplate restTemplate ;
  /**
   *  <p>
   *    groupKey: 服务分组;比如这里调用订单系统就是一个服务分组。模块
   *    commandKey: 服务标识;比如这里订单系统有一个获取订单信息服务。子模块
   *    threadPoolKey: 线程池名称;
   *    threadPoolProperties:线程池配置
   *  </p>
   * @author 爷爷
   * @param id
   * @return Orders
   */
  @HystrixCommand(fallbackMethod = "defaultOrder", 
      groupKey = "orders", 
      commandKey = "getOrder",
      threadPoolKey = "order-pool",
      threadPoolProperties = {
          @HystrixProperty(name = "coreSize", value = "10"),
          @HystrixProperty(name = "keepAliveTimeMinutes", value = "5"),
          @HystrixProperty(name = "maxQueueSize", value = "1000000"),
          @HystrixProperty(name = "queueSizeRejectionThreshold", value = "1000")
      },
      commandProperties = {
          @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
          @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
      }
    )
  public Orders getOrder(Long id) {
    System.out.println(Thread.currentThread() + ", start") ;
    Orders res = restTemplate.getForObject("http://localhost:9810/orders/queryOrder/{1}", Orders.class, id);
    System.out.println(Thread.currentThread() + ", end") ;
    return res ;
  }


  public Orders defaultOrder(Long id) {
    return new Orders() ;
  }


}

这里具体注解属性的说明与方式1中 一一对应。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2020-01-10 11:18:17

Hystrix架构系统

2017-04-03 21:52:30

隔离线程池分布式

2017-07-04 17:35:46

微服务架构Spring Clou

2024-04-16 00:00:00

Spring微服务架构

2011-04-14 10:18:20

数据迁移

2024-02-27 08:22:56

2013-02-18 08:36:51

powershell

2023-06-29 07:55:52

Quartz.Net开源

2010-01-13 17:47:59

VB.NET拖放

2009-12-17 14:36:57

Ruby on Rai

2020-10-09 07:56:52

Linux

2023-10-20 08:01:08

2010-01-07 13:53:43

Linux入门

2024-06-12 00:00:01

Java函数式接口

2010-01-18 19:36:52

VB.NET调整控件

2010-08-05 16:08:12

轻松掌握DB2 9.5

2024-02-28 10:20:08

2023-08-31 08:34:07

Users对象序列化

2024-06-27 10:50:01

2023-09-20 10:07:26

Linux虚拟化
点赞
收藏

51CTO技术栈公众号