使用详解

在前面我们已经使用了Hystrix中的核心注解@HystrixCommand,通过它创建了HystrixCommand的实现,同时利用fallback属性指定了服务降级的实现方法。但是这都是Hystrix的一小部分,在实现一个大型分布式系统时,还需要更多高级的配置功能,接下来就详细学习Hystrix各接口和注解的使用方法。

自定义HystrixCommand

Hystrix命令就是之前所说的HystrixCommand,它用来封装具体的依赖服务调用逻辑。在前面是使用了@HystrixCommand注解的方式,其实也可以采用自定义类然后继承HystrixCommand抽象类的方式。

第一步,在之前的ribbon-consumer项目内新建pojo包,并在里面新建一个Movie类,其中的代码为:

1
2
3
4
5
6
public class Movie {
private String name;
private double price;
private String author;
//getter、setter、toString和有参、无参的构造方法
}

第二步,接着在service包内新建MovieCommand类,让它继承HystrixCommand抽象类,并在MovieCommand中注入RestTemplate,然后重写getFallback和run方法。其中getFallback方法将在服务调用失败时调用;而run方法则是在执行请求时调用。注意还需要提供一个构造方法,该构造方法的第一个参数Sette用于保存一些分组信息,第二个参数RestTemplate则用于调用相关服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MovieCommand extends HystrixCommand<Movie> {
@Autowired
private RestTemplate restTemplate;

protected MovieCommand(Setter setter,RestTemplate restTemplate) {
super(setter);
this.restTemplate = restTemplate;
}

@Override
protected Movie getFallback() {
return new Movie("狂人日记",128,"鲁迅");
}

@Override
protected Movie run() throws Exception {
return restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie",Movie.class);
}
}

在这个run方法中我们调用了服务名为HELLO-SERVICE的getOneMovie方法,当这个接口调用出错的时候就会执行getFallback方法中的逻辑。

第三步,然后回到ConsumerController类中,将其代码修改为如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
public class ConsumerController {
@Autowired
private HelloService helloService;

@Autowired
private RestTemplate restTemplate;

@GetMapping(value = "/ribbon-consumer")
public String helloConsumer(){
return helloService.helloConsumer();
}

@GetMapping(value = "/testOne")
public Movie testOne(){
MovieCommand movieCommand = new MovieCommand(HystrixCommand.Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("")),restTemplate);

//同步调用
Movie movie= movieCommand.execute();
return movie;
}
}

定义一个testOne方法,注意这里只是调用了同步方法,即前面所说的execute方法。

第四步,接着回到服务名为hello-service的工程,这是服务提供者(项目名称为eureka-client),然后在其项目目录下新建pojo包,并在里面新建一个Movie类,其中的代码为:

1
2
3
4
5
6
public class Movie {
private String name;
private double price;
private String author;
//getter、setter、toString和有参、无参的构造方法
}

第五步,然后回到之前的HelloController类中,在里面新增getOneMovie方法,其中的代码为:

1
2
3
4
@GetMapping(value = "/getOneMovie")
public Movie getOneMovie(){
return new Movie("茶馆",118,"老舍");
}

第六步,接着需要启动以下信息,,注意启动的顺序不能颠倒:
(1)服务名为eureka-server的工程,这是服务注册中心,注意端口为1111,即启动实例名为peer1的服务实例。
(2)服务名为hello-service的工程,这是服务提供者,注意需要启动两个实例,启动的端口分别为8081和8082,即启动实例名为p1和p2的服务实例(注意两个服务提供者均需往服务注册中心注册)。
(3)服务名为ribbon-consumer的工程,这是服务消费者,注意需要启动一个实例,启动的端口为9000,即启动实例名为ribbon-consumer的服务实例。

第七步,访问http://localhost:9000/testOne链接,然后页面显示如下信息:

此时如果我们停止其中一个服务提供者实例的运行,此时再访问该链接,就会间隔看到如下页面:

在前面我们执行的方式是同步执行,使用的方式为Movie movie= movieCommand.execute();,其实还可以使用异步执行,此时使用的方式为Future<Movie> futureMovie = movieCommand.queue();,异步执行的时候,可以通过对返回的futureMovie对象,调用它的get方法来获取Movie对象,可以在get方法内传入超时参数。此时的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class ConsumerController {
@Autowired
private HelloService helloService;

@Autowired
private RestTemplate restTemplate;

@GetMapping(value = "/testOne")
public Movie testOne() throws ExecutionException, InterruptedException {
MovieCommand movieCommand = new MovieCommand(HystrixCommand.Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("")),restTemplate);

//异步调用
Future<Movie> futureMovie = movieCommand.queue();
Movie movie = futureMovie.get();
return movie;
}
}

通过注解实现异步请求

如果你觉得这种方式较为麻烦,可以使用@HystrixCommand注解来更为优雅的实现Hystrix命令实现。在service包内新建MovieService类,相应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
@Service
public class MovieService {

@Autowired
private RestTemplate restTemplate;

@HystrixCommand
public Movie getMoive(){
return restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie",Movie.class);
}
}

这样看似乎使用@HystrixCommand注解可以非常优雅的定义Hystrix命令的实现,但是需要注意的是上面定义的getMoive方法只是同步执行的实现,如果你想实现异步执行则还需要另外定义,这个过程需要分为三个步骤:

(1)配置HystrixCommandAspect的Bean

打开robbin-consumer项目,在其入口类RibbonConsumerApplication中定一个用于返回HystrixCommandAspect对象的方法,注意这是一个Bean,相应的代码为:

1
2
3
4
@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}
(2)通过AsyncResult来执行调用

依旧使用@HystrixCommand,并配置AsyncResult对象来实现。在MovieService类中定义futureMovie方法,该方法用于返回一个Future对象,如下所示:

1
2
3
4
5
6
7
8
9
@HystrixCommand
public Future<Movie> futureMovie(){
return new AsyncResult<Movie>() {
@Override
public Movie invoke() {
return restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie",Movie.class);
}
};
}
(3)异步调用实现

在ConsumerController类中添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class ConsumerController {
//其他代码
@Autowired
private MovieService movieService;

//采用注解@HystrixCommand实现的异步方法
@GetMapping(value = "/testTwo")
public Movie testTwo() throws ExecutionException, InterruptedException {
Future<Movie> movieFuture = movieService.futureMovie();
Movie movie = movieFuture.get();
return movie;
}
}

需要说明的是movieFuture对象的get方法是存在多个重载方法的,里面存在可以传入超时参数的方法。

支持响应式函数编程

除了传统的同步执行与异步执行外,我们还可以将HystrixCommand通过Observable来实现响应式执行方式。通过调用observe()toObservable()方法可以返回Observable对象,如下所示:

1
2
Observable<Movie> hotObservable = new MovieCommand(restTemplate).observe();
Observable<Movie> coldObservable = new MovieCommand(restTemplate).toObservable();

因此可以在ConsumerController类中定义testThree方法,其中的代码如下所示:

1
2
3
4
5
6
7
8
//响应式编程支持
@GetMapping(value = "/testThree")
public void testThree(){
MovieCommand movieCommand = new MovieCommand(HystrixCommand.Setter.withGroupKey(
HystrixCommandGroupKey.Factory.asKey("")),restTemplate);
Observable<Movie> hotObservable = movieCommand.observe();
Observable<Movie> coldObservable = movieCommand.toObservable();
}

需要说明的是observe()toObservable()方法虽然都返回了Observable对象,但是两者是不同的,其中observe()方法返回的是一个Hot Observable,该命令会在observe()方法调用的时候立即执行,当Observable每次被订阅的时候会重放它的行为;而toObservable()方法返回的是一个Cold Observable,该命令不会在toObservable()方法执行之后被立即执行,只有当所有订阅者都订阅它之后才会执行。其实这两个方法的区别在前面我们就已经学过,这里就不再细说了。

虽然HystrixCommand具备了observe()toObservable()方法的功能,但是它的实现具有一定的局限性,它返回的Observable只能发射一次数据,所以Hystrix还提供了另外一个特殊的命令封装HystrixObservableCommand,通过它实现的命令可以获取能发射多次的Observable对象。

通过继承HystrixObservableCommand类实现

如果使用HystrixObservableCommand来实现命令封装,需要将命令的执行逻辑在construct方法中重载,这样Hystrix才能将具体逻辑包装到Observable内。在ribbon-consumer项目的service包内新建一个MovieObservableCommand类,让它继承HystrixObservableCommand这个抽象类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MovieObservableCommand extends HystrixObservableCommand<Movie> {

private RestTemplate restTemplate;

protected MovieObservableCommand(Setter setter,RestTemplate restTemplate) {
super(setter);
this.restTemplate =restTemplate;
}

@Override
protected Observable<Movie> construct() {
return Observable.create(new Observable.OnSubscribe<Movie>() {
@Override
public void call(Subscriber<? super Movie> subscriber) {
try{
if(!subscriber.isUnsubscribed()){
Movie movie = restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie", Movie.class);
subscriber.onNext(movie);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}
}
通过使用@HystrixCommand注解实现

可能有人觉得这种继承方式太复杂了,是否存在注解方式实现呢?答案是肯定的,只不过依旧是使用@HystrixCommand,且方法定义需要做一些变化,具体内容与construct()的实现类似。打开MovieService类,在里面新增theNewMovie方法,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@HystrixCommand
public Observable<Movie> theNewMovie(){
return Observable.create(new Observable.OnSubscribe<Movie>() {
@Override
public void call(Subscriber<? super Movie> subscriber) {
try{
if(!subscriber.isUnsubscribed()){
Movie movie = restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie", Movie.class);
subscriber.onNext(movie);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}

注意当你选择使用@HystrixCommand注解的方式来实现响应式命令时,可以通过observableExecutionMode参数来控制选择使用observe()还是toObservable()这两种执行方式中的哪一种,它有两个值:EAGER和LAZY,其中EAGER表示使用observe()方法,而LAZY表示使用toObservable()方法。

服务降级

其实所谓的服务降级,就是服务执行失败了。从上面的介绍中可以知道,fallback是Hystrix命令执行失败后会调用的方法,通过它来实现服务的降级处理逻辑。既然前面说过HystrixCommand存在两种实现方式注解和继承类的方式,那么此处的服务降级方法调用也相应的存在两种方式。

在继承类中的调用

(1)HystrixCommand实现的Hystrix命令。当开发者选择使用自定义HystrixCommand并继承HystrixCommand类的方式来调用服务的方式时,可以在自定义HystrixCommand类中通过重载getFallbackI()方法来实现服务降级逻辑,然后Hystrix会在run()方法执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时,执行getFallbackI()方法内的逻辑。

回到之前的MovieCommand类中,使用如下方式来实现服务降级逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MovieCommand extends HystrixCommand<Movie> {
@Autowired
private RestTemplate restTemplate;

public MovieCommand(Setter setter, RestTemplate restTemplate) {
super(setter);
this.restTemplate = restTemplate;
}

@Override
protected Movie getFallback() {
return new Movie("狂人日记",128,"鲁迅");
}

@Override
protected Movie run() throws Exception {
return restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie",Movie.class);
}
}

(2)HystrixObservableCommand实现的Hystrix命令。当然了,如果是HystrixObservableCommand实现的Hystrix命令时,我们可以通过重载resumeWithFallback方法来实现服务降级逻辑。该方法会返回一个Observable对象,当命令执行失败的时候,Hystrix会将Observable中的结果通知给所有的订阅者。

回到之前的MovieObservableCommand类中,使用如下方式来实现服务降级逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MovieObservableCommand extends HystrixObservableCommand<Movie> {

@Autowired
private RestTemplate restTemplate;

protected MovieObservableCommand(Setter setter,RestTemplate restTemplate) {
super(setter);
this.restTemplate =restTemplate;
}

@Override
protected Observable<Movie> resumeWithFallback() {
return super.resumeWithFallback();
}

@Override
protected Observable<Movie> construct() {
return Observable.create(new Observable.OnSubscribe<Movie>() {
@Override
public void call(Subscriber<? super Movie> subscriber) {
try{
if(!subscriber.isUnsubscribed()){
Movie movie = restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie", Movie.class);
subscriber.onNext(movie);
subscriber.onCompleted();
}
}catch (Exception e){
subscriber.onError(e);
}
}
});
}
}

在注解中的调用

如果开发者想使用注解来定义服务降级逻辑时,我们需要将具体的Hystrix命令与fallback实现函数定义在同一个类中,并且fallbackMethod的值必须与实现的fallback方法的名字相同。由于必须定义在一个类中,因此对于fallback方法的访问修饰符不做要求。

回到之前的MovieService类中,使用如下方式来实现服务降级逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class MovieService {
@Autowired
private RestTemplate restTemplate;

@HystrixCommand(fallbackMethod = "defaultMovie")
public Movie getMoive(){
return restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie",Movie.class);
}

public Movie defaultMovie(){
return new Movie("射雕英雄传",18,"金庸");
}
}

需要说明的是,上面的defaultMovie()方法将在getMoive()方法执行发生错误的情况下被执行。如果defaultMovie()方法实现的并不是一个稳定逻辑,那么它依然可能会发生异常,此时我们也可以给它添加@HystrixCommand注解以生成Hystrix命令,同时使用fallbackMethod参数来指定服务降级逻辑,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class MovieService {
@Autowired
private RestTemplate restTemplate;

@HystrixCommand(fallbackMethod = "defaultMovie")
public Movie getMoive(){
return restTemplate.getForObject("http://HELLO-SERVICE/getOneMovie",Movie.class);
}

@HystrixCommand(fallbackMethod = "defaultSecondMovie")
public Movie defaultMovie(){
return new Movie("射雕英雄传",18,"金庸");
}

public Movie defaultSecondMovie(){
return new Movie("倚天屠龙记",28,"金庸");
}
}

在实际使用过程中,我们需要为大多数执行过程中可能会失败的Hystrix命令实现服务降级逻辑,但是也有一些情况是允许不去实现降级逻辑的,一般来说是如下两种情况:

  • 执行写操作的命令。当Hystrix命令是用来执行写操作,而不是返回一些信息的时候,通常情况下这类操作的返回类型是void或是为空的Observable,实现服务降级的意义不是很大。当写入操作失败的时候,我们通常只需要通知调用者即可。

  • 执行批处理或离线计算的命令。当Hystrix命令是用来执行批处理程序生成一份报告或者是进行任何类型的离线计算时,那么通常这些操作只需要将错误传播给调用者,然后让调用者稍后重试,而不是发送给调用者一个静默的降级处理响应。

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