Spring Cloud Hystrix —— 服务容错保护

Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。它是基于Netflix的开源框架Hystrix实现的,该框架的目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级(fallback)、服务熔断、线程和信号隔离、请求缓存、请求合并以及服务监控等强大功能。

What Is Hystrix For?

  • 保护和控制对依赖项的网络延时和失败情况
  • 防止级联故障
  • 快速失败、快速恢复
  • fallback和服务降级
  • 接近实时级的监控、报警和控制

How Does Hystrix Accomplish Its Goals?

  • 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;
  • 支持自定义依赖项的超时时间,一般稍高于99.5%的访问时间;
  • 每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
  • 记录请求成功,失败,超时和线程拒绝。
  • 服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
  • 请求失败,被拒绝,超时或熔断时执行降级逻辑。
  • 近实时地监控指标和配置的修改。


    实现方法

设计模式

命令模式

观察者模式

舱壁模式

原理分析

工作流程

工作流程
  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对象。

  • execute()

以同步堵塞方式执行run(),只支持接收一个值对象。hystrix会从线程池中取一个线程来执行run(),并等待返回值。

  • queue()

以异步非阻塞方式执行run(),只支持接收一个值对象。调用queue()就直接返回一个Future对象。可通过 Future.get()拿到run()的返回结果,但Future.get()是阻塞执行的。若执行成功,Future.get()返回单个返回值。当执行失败时,如果没有重写fallback,Future.get()抛出异常。

  • observe()

事件注册前执行run()/construct(),支持接收多个值对象,取决于发射源。调用observe()会返回一个hot Observable,也就是说,调用observe()自动触发执行run()/construct(),无论是否存在订阅者。

如果继承的是HystrixCommand,hystrix会从线程池中取一个线程以非阻塞方式执行run();如果继承的是HystrixObservableCommand,将以调用线程阻塞执行construct()。

observe()使用方法:

  1. 调用observe()会返回一个Observable对象
  2. 调用这个Observable对象的subscribe()方法完成事件注册,从而获取结果
  • toObservable()

事件注册后执行run()/construct(),支持接收多个值对象,取决于发射源。调用toObservable()会返回一个cold Observable,也就是说,调用toObservable()不会立即触发执行run()/construct(),必须有订阅者订阅Observable时才会执行。

如果继承的是HystrixCommand,hystrix会从线程池中取一个线程以非阻塞方式执行run(),调用线程不必等待run();如果继承的是HystrixObservableCommand,将以调用线程堵塞执行construct(),调用线程需等待construct()执行完才能继续往下走。

toObservable()使用方法:

  1. 调用observe()会返回一个Observable对象
  2. 调用这个Observable对象的subscribe()方法完成事件注册,从而获取结果

需注意的是,HystrixCommand也支持toObservable()和observe(),但是即使将HystrixCommand转换成Observable,它也只能发射一个值对象。只有HystrixObservableCommand才支持发射多个值对象。

断路器原理

circute breaker

依赖隔离

Hystrix为每个以来的服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用有影响,而不会拖慢其他依赖服务。

另外Hystrix中除了可使用线程池之外,还可以使用信号量来控制单个以来服务的并发度,信号量的开销远比线程池的开销小,但是它不能设置超时和实现异步访问。

使用详解

构建一个如下架构图的服务调用关系

example

分析上述架构图,主要有以下几项工作:

  1. eureka-server工程: 服务注册中心,端口1111
  2. hello-service工程: HELLO-SERVICE服务单元,启动两个实例,端口分别为8081和8082
  3. ribbon-consumer工程: 使用Ribbon实现的服务消费者,端口9000

    下面我们开始引入Spring Cloud Hystrix。
  • 在ribbeon-consumer工程中引入依赖:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
  • 在ribbon-consumer工程主类ConsumerApplication中使用@Enable-CircuitBreaker注释开启断路器功能:
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerApplication {

  @Bean
  @LoadBalanced
  RestTemplate restTemplate() {
    return new RestTemplate();
  }

  public static void main (String[] args) {
    SpringApplication.run(ConsumerApplication.class, args);
  }
}

HystrixCommand支持继承和注释两种写法,本文只介绍注释方法,对继承方法感兴趣的同学可以baidu一些资料。

  • 同步执行
public class UserService {
  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand
  public User getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
  }
}
  • 异步执行
public class UserService {
  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand
  public Future<User> getUserByIdAsync(final String id) {
    return new AsyncResult<User>() {
      @Override
      public User invoke() {
        return restTemplate.getForObject("http://USER-SERVICE/users/{1}", User.class, id);
      }
    }
  }
}

除了传统的同步执行与异步执行之外,我们还可以将HystrixCommand通过Observale来实现响应式执行方式。

public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER)
  public Observable<String> getUserById(final String id) {
    return Observable.create(new Observable.OnSubscribe<String>() {
      @Override
      public void call(Subscriber<? super String> observer) {
        try {
          if (!observer.isUnsubscribed()) {
            String user = restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
            observer.onNext(user);
            observer.onCompleted();
          }
        } catch (Exception e) {
          observer.onError(e);
        }
      }
    });
  }
}

下面是一个简单的Observable的使用示例。关于响应式编程,有兴趣的可以研究一下RxJava。

public void useObservable () {
  Observable<String> observable = userService.getUserById("1");
  observable.subscribe(new Action1<String>() {
    @Override
    public void call(String s) {
      //
    }
  });
}
  • 定义服务降级

fallback是Hystrix命令执行失败时使用的后备方法,用来实现服务的降级处理逻辑。在HystrixCommand中可以通过重载getFallBack()方法来实现服务降级逻辑,Hystrix会再run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况,执行getFallback()方法内的逻辑。

public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "defaultUser")
  public String getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
  }

  public String defaultUser() {
    return "default user";
  }

}

如果fallback逻辑还是不稳定,可以添加多级fallback逻辑。

public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "defaultUser")
  public String getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
  }

  @HystrixCommand(fallbackMethod = "defaultUserSec")
  public String defaultUser() {
    return "first default user";
  }

  public String defaultUserSec() {
    return "second default user";
  }

}

不论Hystrix命令是否实现了服务降级,命令状态和断路器状态都会更新,并且我们可以由此了解到命令执行的失败情况。

  • 忽略异常
public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCommand(fallbackMethod = "defaultUser", ignoreExceptions = {HystrixBadRequestException.class})
  public String getUserById(Long id) {
    return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
  }

  public String defaultUser(Long id, Throwable e) {
    return "default user";
  }

}
  • 命名名称、分组以及线程池划分
@HystrixCommand(commandKey = "getUserById", groupKey = "UserGroup", threadPoolKey = "getUserByIdThread")
public String getUserById(Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
}
  • 请求缓存
@CacheResult
@HystrixCommand
public String getUserById(@CacheKey("id") Long id) {
  return restTemplate.getForObject("http://USER-SERVICE/users/{1}", String.class, id);
}

@CacheRemove(commandKey = "getUserById")
@HystrixCommand
public void update(@CacheKey("id") User user) {
  return restTemplate.postForObject("http://USER-SERVICE/users/{1}", user, User.class);
}
  • 请求合并
    HystrixCollapser可以实现对以来服务请求的合并,以减少通信小号和线程数的占用。
@Service
public class UserService {

  @Autowired
  private RestTemplate restTemplate;

  @HystrixCollapser(batchMethod = "findAll", collapserProperties = {
      @HystrixProperty(name = "timeDelayInMilliseconds", value = "100")
  })
  public String find(Long id) {
    return null;
  }

  @HystrixCommand
  public List<String> findAll(List<Long> ids) {
    return restTemplate.getForObject("http://USER-SERVICE/users?ids={1}", List.class,
        StringUtils.join(ids, ","));
  }

}

属性详解

Hystrix仪表盘

Turbine集群监控

Reference
Netflix/Hystrix Wiki
Netflix/Hystrix Wiki How-To-Use
Hystrix原理与实战

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

推荐阅读更多精彩内容