创建请求命令
Hystrix命令就是我们之前说的HystrixCommand,它用来封装具体的依赖服务调用逻辑。
我们可以通过继承的方法实现,重写run方法执行命令,并且重写getFallback方法实现降级。比如:
public class UserCommand extends HystrixCommand<User>{
private RestTemplate restTemplate;
private String name;
private int age;
public UserCommand(RestTemplate restTemplate, String name, int age) {
super(HystrixCommandGroupKey.Factory.asKey("exampleGroup"));
this.restTemplate = restTemplate;
this.name = name;
this.age = age;
}
@Override
protected User run() throws Exception {
//模拟异常,测试降级策略
//int a = 1/0;
User user = restTemplate.getForObject("http://HELLO-SERVICE/user2?name={1}&age={2}", User.class, name,age);
return user;
}
/**
* 降级。Hystrix会在run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时,
* 执行getFallBack()方法内的逻辑
*/
@Override
protected User getFallback() {
return new User("error",0);
}
}
通过这个UserCommand,我们既可以实现请求的同步执行也可以实现异步执行。
同步执行
/**
* 测试HystrixCommand同步执行,使用继承的方式
* @return
*/
public String hello1(){
User user = new UserCommand(restTemplate, "xiaoming", 21).execute();
return user.toString();
}
异步执行
/**
* 测试HystrixCommand异步执行,使用继承的方式
* @return
*/
public String hello2() throws InterruptedException, ExecutionException{
Future<User> futureUser = new UserCommand(restTemplate, "xiaoming", 21).queue();
//异步执行通过get获取结果
User user = futureUser.get();
return user.toString();
}
另外也可以使用@HystrixCommand注解来实现:
同步执行:
/**
* 测试HystrixCommand同步执行,使用@HystrixCommand注解
* @return
*/
@HystrixCommand
public String hello3(){
User user = restTemplate.getForObject("http://HELLO-SERVICE/user2?name={1}&age={2}", User.class, "xiaogang",12);
return user.toString();
}
异步执行:
/**
* 测试HystrixCommand异步执行,使用@HystrixCommand注解
* @return
*/
@HystrixCommand
public Future<User> hello4(){
return new AsyncResult<User>(){
@Override
public User invoke(){
return restTemplate.getForObject("http://HELLO-SERVICE/user2?name={1}&age={2}", User.class, "xiaogang",12);
}
};
}
除了传统的同步执行与异步执行以外,我们还可以将HystrixCommand通过Observable来实现响应式执行方式。通过调用observe()方法和toObservable()方法可以返回Observable对象,比如:
/**
* 返回的是Hot Observable,HotObservable,不论 “事件源” 是否有“订阅者” 都会在创建后对事件进行发布。所以对于Hot
* Observable的每一个“订阅者”都有 可能从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程
*/
public void hello5() {
Observable<User> hoUser = new UserCommand(restTemplate, "xiaoming21", 21).observe();
hoUser.subscribe(new Observer<User>() {
@Override
public void onCompleted() {
System.out.println("==============onCompleted");
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
}
@Override
public void onNext(User user) {
System.out.println("=========onNext: " + user);
}
});
hoUser.subscribe(new Action1<User>() {
@Override
public void call(User user) {
System.out.println("==================call:" + user);
}
});
}
/**
* Cold Observable在没有 “订阅者” 的时候并不会发布时间, 而是进行等待,知道有 “订阅者” 之后才发布事件,所以对于 Cold
* Observable的订阅者,它可以保证从一开始看到整个操作的全部过程。
*/
public String hello6() {
Observable<User> coUser = new UserCommand(restTemplate, "xiaoming22", 22).toObservable();
return coUser.toBlocking().single().toString();
}
虽然HystrixCommand具备了observe()和toObservable()的功能,但是它的实现有一定的局限性,它返回的Observable只能发射一次数据,所以Hystrix还提供了HystrixObservableCommand, 通过它实现的命令可以获取能发多次的Observable。
如果使用HystrixObservableCommand来实现命令封装,需要将命令的执行逻辑在construct方法中执行,并重写 resumeWithFallback方法来实现服务降级。
public class UserObservableCommand extends HystrixObservableCommand<String> {
private RestTemplate restTemplate;
private String name;
private int age;
public UserObservableCommand(RestTemplate restTemplate, String name, int age) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.restTemplate = restTemplate;
this.name = name;
this.age = age;
}
@Override
protected Observable<String> construct() {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
if(!subscriber.isUnsubscribed()) {
//模拟异常,测试降级策略
//int a = 1/0;
User user = restTemplate.getForObject("http://HELLO-SERVICE/user2?name={1}&age={2}", User.class, name,age);
subscriber.onNext(user.toString());
subscriber.onNext(new User("测试",123).toString());
subscriber.onCompleted();
}
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
@Override
protected Observable<String> resumeWithFallback() {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
if (!subscriber.isUnsubscribed()) {
subscriber.onNext("失败了!");
subscriber.onNext("找大神来排查一下吧!");
subscriber.onCompleted();
}
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
}
public void hello7() {
Observable<String> observable= new UserObservableCommand(restTemplate,"xiaoer",1).observe();
Iterator<String> iterator = observable.toBlocking().getIterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
public void hello8() {
Observable<String> observable= new UserObservableCommand(restTemplate,"xiaoer",1).toObservable();
Iterator<String> iterator = observable.toBlocking().getIterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
而对此的注解依然是@HystrixCommand,只是方法定义需要变化,具体内容与construct()的实现类似,如下所示:
/**
* EAGER参数表示使用observe()方式执行
*/
@HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER, fallbackMethod = "observFailed") //使用observe()执行方式
public Observable<String> hello9(final Long id) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
if(!subscriber.isUnsubscribed()) {
subscriber.onNext("张三的ID:");
int i = 1 / 0; //抛异常,模拟服务降级
subscriber.onNext(String.valueOf(id));
subscriber.onCompleted();
}
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
private String observFailed(Long id) {
return "observFailed---->" + id;
}
/**
* LAZY参数表示使用toObservable()方式执行
*/
@HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY, fallbackMethod = "toObserbableError") //表示使用toObservable()执行方式
public Observable<String> hello10(final String name) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
try {
if(!subscriber.isUnsubscribed()) {
subscriber.onNext("找到");
subscriber.onNext(name);
int i = 1/0; ////抛异常,模拟服务降级
subscriber.onNext("了");
subscriber.onCompleted();
}
} catch (Exception e) {
subscriber.onError(e);
}
}
});
}
private String toObserbableError(String name) {
return "toObserbableError--->" + name;
}
@GetMapping("hello9")
public String hello9(){
Iterator<String> iterator = helloService.hello9(30L).toBlocking().getIterator();
while(iterator.hasNext()) {
System.out.println("===============" + iterator.next());
}
return "";
}
@GetMapping("hello10")
public String hello10(){
Iterator<String> iterator = helloService.hello10("张三").toBlocking().getIterator();
while(iterator.hasNext()) {
System.out.println("===============" + iterator.next());
}
return "";
}
在使用@HystrixCommand注解实现响应命令时,可以通过observableExecutionMode参数来控制使用observe()还是toObservable()的执行方法。
- @HystrixCommand(observableExecutionMode = ObservableExecutionMode.EAGER):使用observe()方式
- @HystrixCommand(observableExecutionMode = ObservableExecutionMode.LAZY):使用toObservable()方式
定义服务降级
在继承HystrixCommand时,通过重写getFallback()方法来实现服务的降级处理逻辑,当run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时会执行此方法内的逻辑。具体代码在上面可以找到。
在继承HystrixObservableCommand时,通过重写resumeWithFallback()方法来实现服务降级逻辑,该方法会返回Observable对象,当命令执行失败时,Hystrix会将Observable中的结果通知给所有订阅者。
若通过注解实现服务降级则只需要使用@HystrixCommand中的fallbackMethod参数来指定具体的服务降级实现方法。
@HystrixCommand(fallbackMethod = "helloFallback")
public String hello() {
String result = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
return result;
}
public String helloFallback() {
return "error";
}
注意Hystrix命令与fallback实现方法必须在一个类中,并且fallbackMethod的值必须与实现fallback方法的名字相同,对于该方法额访问修饰符没有要求,private、protected、public都可以。
下面看一个例子:
@HystrixCommand(fallbackMethod = "defaultfallback")
public String test() {
String result = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
return result;
}
@HystrixCommand(fallbackMethod = "defaultfallbackSec")
private String defaultfallback() {
//可能这个方法依然也有网络请求
String result = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
return result;
}
private String defaultfallbackSec() {
return "error";
}
如果fallbackMethod指定的方法并不是一个稳定的逻辑,它依然可能发生异常,那么我们也可以为它添加@HystrixCommand注解并通过fallbackMethod指定服务降级逻辑,直到服务降级逻辑是一个稳定逻辑为止。
大多数情况下我们需要为可能失败的Hystrix命令实现服务降级逻辑,但也有一些情况不需要去实现降级逻辑,如:
执行写操作的命令:当Hystrix命令是执行写操作而不是返回一些信息时,这类操作的返回类型一般是void或者是空的Observable,实现服务降级的意义不是很大,当操作失败时我们只需要通知调用者即可。
执行批处理或离线计算的命令:当Hystrix命令是用来执行批处理程序生成报告或进行离线技术时,通常只需要将错误传播给调用者让调用者稍后重试而不是发送给调用者一个静默的降级处理响应。
异常处理
异常传播
在HystrixCommand实现的run()方法中抛出异常时,除了HystrixBadRequestException之外,其它异常都会被Hystrix认为命令执行失败并触发服务降级的处理逻辑,所以当需要在命令执行中抛出不触发服务降级的异常时来使用它。
在使用注解实现Hystrix命令时可以通过设置@HystrixCommand注解的ignoreExceptions参数来指定忽略的异常类型。
/**
* 当设置ignoreExceptions参数时,
* 抛出对应的异常就不会触发降级(也就是不会调用failMethod()方法).
*/
@HystrixCommand(
ignoreExceptions = {NullPointerException.class, ArithmeticException.class},
fallbackMethod = "failMethod"
)
public String getUserName(Long id) {
Long re = id/0; //会抛ArithmeticException
String param = null;
param.trim(); // 此处会抛NullPointException
return "张三";
}
private String failMethod(Long id, Throwable e) {
return e.getMessage();
}
异常获取
通过继承实现的Hystrix命令中,可以在getFallback()方法中通过Throwable getExecutionException()方法来获取具体的异常。
@Override
protected User getFallback() {
Throwable ex = getFailedExecutionException();
System.out.println(ex.getMessage());
return new User("error",0);
}
通过注解实现的Hystrix命令,只需要在fallback实现方法的参数中增加Throwable e对象的定义就可。
private String failMethod(Long id, Throwable e) {
return e.getMessage();
}
命名名称、分组以及线程池划分
以继承方式实现的Hystrix命令使用类名作为默认的命名名称,我们也可以在构造方法中通过Setter静态类来设置。
public UserCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CommandName")));
}
从上面Setter的使用可以看到,我们没有直接设置命令名称,而是先调用了withGroupKey来设置命令组名,然后调用andCommandKey来设置命令名。这是因为在Setter的定义中,只有withGroupKey静态函数可以创建Setter实例,所以GroupKey是每个Setter必须的参数,CommandKey是一个可选的参数。
那么为什么要设置命令组呢?因为命令组可以实现统计(仪表盘),且Hystrix命令默认的线程划分是根据命令组来实现的。默认情况下,Hystrix会让相同组名的命令使用同一个线程池,所以我们需要在创建Hystrix命令时为其指定命令组名来实现默认的线程池划分。
如果只使用命令组来分配线程池则不够灵活,所以Hystrix还提供了HystrixThreadPoolKey来对线程池进行设置,来实现更细粒度的线程池划分。
public UserCommand() {
super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GroupName"))
.andCommandKey(HystrixCommandKey.Factory.asKey("CommandName"))
.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ThreadPoolKey")));
}
如果没有指定HystrixThreadPoolKey,依然会使用命令组的方式来划分线程池。通常情况下尽量通过HystrixThreadPoolKey的方式来指定线程池的划分,而不是通过组名的默认方式实现划分。
使用注解设置命令名称、分组以及线程池划分可以通过指定commandKey、groupKey和threadPoolKey属性来设置,它们分别代表了命令名称、分组以及线程池划分。
@HystrixCommand(commandKey="userCommand",groupKey="userGroup",threadPoolKey="userThread")
public String hello3() {
User user = restTemplate.getForObject("http://HELLO-SERVICE/user2?name={1}&age={2}", User.class, "xiaogang",
12);
return user.toString();
}