服务降级
在继承HystrixCommand时,通过重写getFallback()方法来实现服务的降级处理逻辑,当run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时会执行此方法内的逻辑。具体代码在上面可以找到。
在继承HystrixObservableCommand时,通过重写resumeWithFallback()方法来实现服务降级逻辑,该方法会返回Observable对象,当命令执行失败时,Hystrix会将Observable中的结果通知给所有订阅者。
若通过注解实现服务降级则只需要使用@HystrixCommand中的fallbackMethod参数来指定具体的服务降级实现方法。
@HystrixCommand(fallbackMethod = "helloFallback")
public String hello() {
String result = restTemplate.getForEntity("http://HELLO-SERVICE/hystrix/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/hystrix/hello", String.class).getBody();
return result;
}
@HystrixCommand(fallbackMethod = "defaultfallbackSec")
private String defaultfallback() {
//可能这个方法依然也有网络请求
String result = restTemplate.getForEntity("http://HELLO-SERVICE/hystrix/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();
}
注意:如果通过注解的方式没有指定commandKey,默认以方法名作为commandKey。