Hystrix

发布时间 2023-06-29 23:29:01作者: lwx_R

1.概念

Hystrix 源自Netflix 团队于2011年开始研发。2012年 Hystrix不断发展和成熟,Netlix 内部的许多团队都采用了它。
如今,每天在Netlix上通过Hystrix执行数百亿个线程隔离和数千亿个信号量隔离的调用。极大地提高了系统的稳定性。
在分布式环境中,不可避免地会有许多服务依赖项中的某些服务失败而导致雪崩效应
Hystrix 是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。
Hystrix 通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体稳定性。

2.雪崩效应

在微服务架构中,一个请求需要调用多个服务是非常常见的。
如客户端访问A服务,而A服务需要调用B服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应, A服务将处于阻塞状态,直到B服务C服务响应。
此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫疾。
服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的雪崩效应。
在高并发的情况下。一个服务的近迟可能导致所有服务器上的所有资源在数秒内饱和。
比起服务故障,更糟样的是这些应用程序还可能导致服务之间的延迟增加,从而备份队列,线程和其他系统资源,从而导致整个系统出现更多级联故障。
造成雪崩的原因可以归结为以下三点:

  • 服务提供者不可用(硬件故障,程序BUG,缓存击穿,用户大量请求等)
  • 重试加大流量(用户重试,代码逻辑重试)
  • 服务消费者不可用(同步等待造成的资源耗尽)
    最终的结果就是: 一个服务不可用,导致一系列服务的不可用。

3.解决方案

雪崩是系统中的蝴蝶效应导致,其发生的原因多种多样,从源头我们无法完全杜绝雪崩的发生
但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估做好服务容错。解决方案大概可以分为以下几种:

  • 请求缓存:支持将一个请求与返回结果做缓存处理;
  • 请求合并:将相同的请求进行合并然后调用批处理接口;
  • 服务隔离:限制调用分布式服务的资源,某一个调用的服务出现问题不会影响其他服务调用;
  • 服务熔断:牺牲局部服务,保全整体系统稳定性的措施;
  • 服务降级:服务熔断以后,客户端调用自己本地方法返回缺省值。

4.请求缓存

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
  • RedisConfig
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate){
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getKeySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
                return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }
}
  • appliation.yml
spring:
  application:
    name: consumer
  redis:
    timeout: 10000
    host: 192.168.10.101
    port: 6379
    # 选择那个库
    database: 0
    lettuce:
      pool:
        max-active: 1024
        max-wait: 10000
        max-idle: 200
        min-idle: 5
  • OrderServiceImpl
@Override
public Product selectProductById(Integer id) {
   return restTemplate.getForObject("http://product/product/" + id,
           Product.class);
}
  • sevice层
@Cacheable(cacheNames = "orderService:product:list")
 public Order selectOrderById(Integer id) {
      return new Order(id, "order", "lwx", 227D, null);
  }
  • 启动类上
//开启缓存
@EnableCaching

5.请求合并

在微服务架构中,我们将一个项目拆分成很多个独立的模块,这些独立的模块通过远程调用来互相配合工作,
但是,在高并发情况下,通信次数的增加会导致总的通信时间增加
同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。

  • ProductServiceImpl
//合并请求
@HystrixCollapser(batchMethod = "selectProductListByIds",
          //请求方式
          scope = com.netflix.hystrix.HystrixCollapser.Scope.GLOBAL,
          collapserProperties = {
              //间隔多久的请求会进行合并
              @HystrixProperty(name = "timerDelayInMilliseconds", value = "20"),
              //批处理之前,最大请求数
                  @HystrixProperty(name = "maxRequestsInBatch", value = "200")
          })
@Override
public Future<Product> selectProductById(Integer id) {
    
    return null;
}

@HystrixCommand
public List<Product> selectProductListByIds(){
    System.out.println("Muti Ids");
    return null;
}
  • 启动类
@EnableFeignClients
//开启Hystrix
@EnableCircuitBreaker

6.服务隔离

6.1 线程隔离

没有线程池隔离的项目所有接口都运行在一个ThreadPool中,当某一个接口压力过大或者出现故障时,会导致资源耗尽从而影响到其他接口的调用而引发服务雪崩效应。
我们在模拟高并发场景时也演示了该效果。通过每次都开启一个单独线程运行。 它的隔离是通过线程池,即每个隔离粒度都是个线程池,互相不干扰。线程池隔离方式,
等于多了一层的保护措施,可以通过hytrix直接设置超时,超时后直接返回。


  • ProductServiceImpl
//线程隔离
@HystrixCommand(groupKey = "listPool",//服务名称,同名称使用用一个线程池
        commandKey = "selectProductList",//接口名称,默认方法名
        threadPoolKey = "listPool",//线程池名称,同名称同一个线程池
        commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
        },
        threadPoolProperties = {
            //线程池大小
                @HystrixProperty(name = "coreSize", value = "6"),
                //队列等待阈值
                @HystrixProperty(name = "maxQueueSize", value = "100"),
                //线程存活时间
                @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
                //超出队列等待阈值执行拒绝策略
                @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")
        },fallbackMethod = "selectProductListFallback"
)
@Override
public Future<Product> selectProductById(Integer id) {
    System.out.println("single id");
    return null;
}

//拖低数据
private List<Product> selectProductListFallback(){
    return Arrays.asList(
            new Product(1, "lwx",1,5800D)
    );
}

//线程隔离
@HystrixCommand(groupKey = "singlePool",//服务名称,同名称使用用一个线程池
        commandKey = "selectProductById",//接口名称,默认方法名
        threadPoolKey = "singlePool",//线程池名称,同名称同一个线程池
        commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
        },
        threadPoolProperties = {
                //线程池大小
                @HystrixProperty(name = "coreSize", value = "3 "),
                //队列等待阈值
                @HystrixProperty(name = "maxQueueSize", value = "100"),
                //线程存活时间
                @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
                //超出队列等待阈值执行拒绝策略
                @HystrixProperty(name = "queueSizeRejectionThreshold",value = "100")
        }
)
public List<Product> selectProductListByIds(){
    System.out.println("Muti Ids");
    return null;
}

6.2 信号量隔离

每次调用线程,当前请求通过计数信号量进行限制,当信号量大于了最大请求数maxConcurrentRequests 时,进行限制,
调用fallback 接口快速返回。信号量的调用是同步的,也就是说,每次调用都得阻塞调用方的线程,直到结果返回。这样就导
致了无法对访问做超时(只能依靠调用协议超时,无法主动释放).

  • ProductServiceImpl
//信号量隔离
@HystrixCommand(commandProperties = {
        //超时时间
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
        //信号量隔离
        @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_STRATEGY, value = "SEMAPHORE"),
        //信号量最大大小
        @HystrixProperty(name = HystrixPropertiesManager.EXECUTION_ISOLATION_SEMAPHORE_MAX_CONCURRENT_REQUESTS, value="6")
},fallbackMethod = "selectProductListFallback")
public List<Product> selectProductListByIds(){
    System.out.println("Muti Ids");
    return null;
}

6.3 比较

  • 使用线程池隔离可以安全隔离依赖的服务(例如图中A、C. D服务),减少所依赖服务发生故障时的影响面。
    比如A服务发生异常,导致请求大量超时,对应的线程池被打满,这时并不影响C. D服务的调用。
  • 当失败的服务再次变得可用时,线程池将清理并立即恢复,而不需要一 个长时间的恢复。
  • 独立的线程池提高了井发性。
  • 请求在线程池中执行,肯定会带来任务调度、排队和上下文切换带来的CPU开销。
  • 因为涉及到跨线程,那么就存在ThreadLocal数据的传递问题,比如在主线程初始化的ThreadLocal变量,在线程池线程中无
    法获取。

    线程池隔离
  • 请求线程和调用Provider线程不是同一条线程;
  • 支持超时,可直接返回;
  • 支持熔断,当线程池到达最大线程数后,再清求会触发fallback 接口进行熔断;
  • 隔离原理:每个服务单独用线程池;
  • 支持同步和异步两种方式;
  • 资源消耗大,大量线程的上下文切换、排队。调度等,容易造成机器负载高;
  • 无法传递Http Header.
    信号量隔离
  • 请求线程和调用Provider线程是同一条线程;
  • 不支持超时;
  • 支持熔断,当信号量达到maxConcurrentRequests 后。再请求会触发fallback 接口进行熔断;
  • 隔离原理:通过信号量的计数器;
  • 同步调用,不支持异步;
  • 资源消耗小,只是个计数器;
  • 可以传递Http Header.

6.4 总结

  • 请求并发大,耗时长(计算大,或操作关系型数据库),采用线程隔离策略。这样可以保证大量的线程可用,不会由于服务
    原因-直处于阻塞或等待状态,快速失败返回。还有就是对依赖服务的网络请求的调用和访问,会涉及timeout这种问题的都
    使用线程池隔离。
  • 请求并发大,耗时短(计算小,或操作缓存),采用信号量隔离策略,因为这类服务的返回通常会非常的快,不会占用线程
    太长时间,而且也减少了线程切换的开销,提高了缓存服务的效率。还有就是适合访问不是对外部依赖的访问,而是对内部的
    一些比较复杂的业务逻辑的访问, 像这种访问系统内部的代码,不涉及任何的网络请求,做信号量的普通限流就可以了,因为
    不需要去捕获timeout类似的问题,并发量突然太高,稍微耗时一些导致很多线程卡在这里,所以进行一个基本的资源隔离和
    访问,避免内部复杂的低效率的代码,导致大量的线程被夯住。

7.服务熔断

服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措
施,所以很多地方把熔断亦称为过载保护。
熔断机制相当于电路的跳闸功能。
例如:我们可以配置熔断策略为当请求错误比例在10s内> 50%时,该服务将进入熔断状态,后续请求都会进入fallback.

  • ProductServiceImpl
//服务熔断
@HystrixCommand(commandProperties = {
        //10s内请求数大于10个自动熔断,当请求符合熔断条件触发fallbackMethod
        @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
        value = "10"),
        //请求错误率大于50% 自动熔断,然后重试请求
        @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
        value = "50"),
        //熔断多少秒后重试
        @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
        value = "5000"),
},fallbackMethod = "selectProductListFallBack")
public List<Product> selectProductListByIds(){
    System.out.println("Muti Ids");
    return null;
}

8.服务降级

触发条件

  • 方法抛出非HystrixBadRequestException 异常;
  • 方法调用超时;
  • 熔断器开启拦截调用;
  • 线程池/队列/信号量跑满。
 //服务降级
@HystrixCommand(fallbackMethod = "selectProductByIdFallback")
public List<Product> selectProductListByIds(){
    System.out.println("Muti Ids");
    return null;
}

9.Feign雪崩

  • pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • ProductServiceFallback
@Component
public class ProductServiceFallback implements ProductService {

    @Override
    public List<Product> listProducts() {
        return null;
    }

    @Override
    public Product selectProductById(Integer id) {
        return null;
    }

    @Override
    public List<Product> selectProductByIds(List<Integer> ids) {
        return null;
    }
}
  • ProductService
@FeignClient(value = "product-service", fallback = ProductServiceFallback.class)
public interface ProductService {

    @GetMapping("/product/list")
    List<Product> listProducts();

    @GetMapping("/product/{id}")
    Product selectProductById(@PathVariable("id") Integer id);

    @GetMapping("/product/listByIds")
    List<Product> selectProductByIds(@RequestParam("id")List<Integer> ids);
}

9.1 捕获服务异常

可以通过Feign实现服务降级处理,但是服务不可用时如果我们想要捕获异常信息

  • ProductServiceFallbackFactory
@Component
public class ProductServiceFallbackFactory implements FallbackFactory<ProductService> {
    //在需要捕获异常的方法中处理
    Logger logger = LoggerFactory.getLogger(ProductServiceFallbackFactory.class);

    @Override
    public ProductService create(Throwable throwable) {
        return new ProductService() {
            @Override
            public List<Product> listProducts() {
                return null;
            }

            @Override
            public Product selectProductById(Integer id) {
                return null;
            }

            @Override
            public List<Product> selectProductByIds(List<Integer> ids) {
                return null;
            }
        };
    }
}
  • ProductService
@FeignClient(value = "product-service", fallbackFactory = ProductServiceFallbackFactory.class)
public interface ProductService {

    @GetMapping("/product/list")
    List<Product> listProducts();

    @GetMapping("/product/{id}")
    Product selectProductById(@PathVariable("id") Integer id);

    @GetMapping("/product/listByIds")
    List<Product> selectProductByIds(@RequestParam("id")List<Integer> ids);
}
  • 启动类
@EnableFeignClients

10.Hystrix服务监控

10.1 Actuator

除了实现服务容错之外, Hystrix 还提供了近乎实时的监控功能,将服务执行结果和运行指标,请求数量成功数量等等这些状态
通过Actuator 进行收集,然后访问/actuator /hystrix.stream即可看到实时的监控数据。

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • application.yml
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream #端点开启 localhost:9090/actuator/hystrix.stream

10.2 监控中心

所谓的监控中心就是Hystrix提供的一套可视化系统Hystrix-Dashboard , 可以非常友好的看到当前环境中服务运行的状
态。Hystrix- Dashboard是一款针对Hystrix 进行实时监控的工具,通过Hystrix-Dashboard 我们可以直观地看到各
Hystrix Command 的请求响应时间,请求成功率等数据。

  • pom.xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
  • 启动类
//数据监控
@EnableHystrixDashboard

10.3 聚合监控

Turbine是聚合服务器发送事件流数据的一个工具,dashboard 只能监控单个节点,实际生产环境中都为集群,因此可以通过Turbine来监控集群服务。

  • pom.xml
<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
    </dependencies>
  • application.yml
#聚合监控
turbine:
  #要监控的服务列表
  app-config: order-service, order-service-feign
  #指定集群名称
  cluster-name-expression: "default"
  • 启动类
@EnableCircuitBreaker
@EnableHystrixDashboard
@EnableTurbine
@SpringBootApplication
public class hystrixTurbine
{
    public static void main( String[] args )
    {
        SpringApplication.run(hystrixTurbine.class);
    }
}