Hystrix 使用指南(2):Fallback 详解

一、概述

降级是除了熔断以外,Hystrix 的另一个重要功能。简单来说,使用 Hystrix 实现降级功能是通过覆写 HystrixCommand 中的 getFallback() ,在其中实现自定义的降级逻辑实现的。看起来很简单,但我们需要了解 getFallback() 方法的执行原理,以及在不同场景中正确实 Fallback 的方式。本篇文章就来介绍这方面的内容。

二、关于 Fallback 你需要知道的

1. 什么情况下会触发 Fallback

下面三种情况会导致 Hystrix 执行 Fallback

  1. 主方法抛出异常
  2. 主方法执行超时
  3. 线程池拒绝
  4. 断路器打开

主方法抛出异常

通常,当 HystrixCommand 的主方法(run()) 中抛出异常时,便会触发 getFallback()。除了一个例外 —— HystrixBadRequestException。当抛出 HystrixBadRequestException,不论当前 Command 是否定义了 getFallback(),都不会触发,而是向上抛出异常。

如果实现业务时有一些异常希望能够向上抛出,而不是触发 Fallback 策略,便可以封装到 HystrixBadRequestException 中。

关于 HystrixBadRequestException 另一个需要知道的是,它并不会被 Hystrix 计入失败。也就是说,不论一个 Command 抛出多少 HystrixBadRequestException,都不会导致熔断发生。

主方法执行超时

这种情况也很容易理解,在 Hystrix 中,执行超时也是失败的一种,所以同样也会触发 Fallback。

线程池拒绝

线程池拒绝指的是 HystrixCommand 所使用的线程池没有足够的线程执行本次调用。Hystrix 使用的线程池的队列大小默认为 -1。此时,队列类型为 SynchronousQueue。当线程数不足时,任务会就被拒绝。这种情况也算是一种执行失败,所以也会出发 Fallback。

断路器打开

当断路器打开时,Hystrix 会直接执行 Fallback,而不会执行主方法。

2. Fallback 的执行线程

Hystrix 用来执行 getFallback() 方法所使用的线程同执行 run() 方法使用的线程是来自同一个线程池。

因为 getFallback() 中的逻辑虽然通常为降级策略,但仍为程序功能的一部分,依旧需要使用同主逻辑相同的隔离策略。在默认情况下,即线程池隔离。

3. Fallback 是否受超时时间控制

不受。

getFallback() 的执行时间并不受 HystrixCommand 的超时时间的控制。所以,在使用 fallback 机制的时候,要避免 fallback 方法占用过多的线程。

默认情况下,为了避免 getFallback() 方法占用过多的资源,Hystrix 使用了信号量 Semaphore 对其进行资源隔离,默认并发度为10。但信号量的资源隔离的力度是有限的。

假设,如果你在 getFallback() 方法中实现重试逻辑。假如 HystrixCommand 上设置的超时时间是1秒。当熔断发生时,因为你在 getFallback() 中再次调用同一个方法,且因为 getFallback() 不受超时时间的控制。当 TPS 大于 10 时,尽管主方法没有耗尽线程,但 getFallback() 方法也会把线程池耗尽。(假设线程池使用默认10的配置)

三、Fallback 使用模式

在了解了一些关于 Fallback 必须知道的内容之后,我们再来看看在各种场景中如何使用 Hystrix。

1. 简单的 Fallback

有时,Fall back 会很简单。例如,实现用户授权检查功能,如果这个授权检查所保护的资源不是那么关键,你可以这么实现。

class AuthCommand extends HystrixCommand<Boolean> {
  public Boolean run() {
    return authService.authenticate(user);
  }
  
  protected Boolean getFallback() {
    return true;
  }
}

如果 authService 出现错误时,直接返回授权通过。

2. Fallback 中包含网络调用

有些时候,上面那种简单的 Fallback 策略不能满足需要。例如,查看用户订单,当服务故障时,返回一个空的订单列表并不合适,我们可以通过缓存获取订单信息,虽然可能不是最新的,但好过没有任何内容。

class GetOrderListCommand extends HystrixCommand<List<Order>> {
  private Long userId;
  
  GetOrderListCommand(Long userId) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteService")));
    this.userId = userId;
  }
  
  public List<Order> run() {
    return orderQueryService.list(userId);
  }
  
  protected List<Order> getFallback() {
    return new ListOrderFromCacheCommand(userId).execute();
  }
}

class ListOrderFromCacheCommand extends HystrixCommand<List<Order>> {
  private Long userId;
  
  ListOrderFromCacheCommand(Long userId) {
    super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteService"))
        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("RemoteService-Fallback")));
    this.userId = userId;
  }
  
  public List<Order> run() {
    return orderListCache.get(userId);
  }
}

在上面的例子中,GetOrderListCommand 的 Fallback 策略是从分布式缓存中查询数据。因为分布式缓存查询需要通过网络,所以不能直接在 Fallback 中执行,而是需要封装为另一个 HystrixCommand。

原因在上面也提到了,HystrixCommand 的 Fallback 不受执行超时的控制,也会有熔断等功能的保护。如果直接执行网络操作,会影响正常功能的执行。

另外,重试 Command ListOrderFromCacheCommand 还有一个细节需要注意,其线程池需要和主 Command(这里是 GetOrderListCommand)的线程池不同。此例中是通过区分 Thread Pool Key 实现的。

3. 重试

重试是一个常见的提高可用性的设计,但不恰当地使用重试,也会降低可用性。例如,当依赖的服务已经发生严重故障时,再进行重试,只会使问题雪上加霜。

通过 Hystrix 实现重试,可以避免上述问题:

class RetryDemoCommand extends HystrixCommand<String> {
  public String run() {
    return doService();
  }
  
  public String getFallback() {
    if (!isCircuitBreakerOpen()) {
      return doService();
    } else {
      return DEFAULT;
    }
  }
}

在上面的代码中,RetryDemoCommand 的 Fallback 方法先通过 isCircuitBreakerOpen() 方法判断当前 Command 断路器的状态,如果不是断路状态,才会进行重试,否则就返回默认值。

这里需要主要,如果 doService 里是一次远程调用,就需要安装上例的方式用另一个 Command 进行封装保护。

四、总结

本文介绍了关于 Hystrix Fallback 机制的细节知识和 Fallback 使用模式。希望能对大家更好地使用 Hystrix 起到帮助。

欢迎大家关注我的个人公众号:

我的技术公众号“编走编想”

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352