带有业务异常的 Hystrix 断路器
Hystrix circuit breaker with business exceptions
我观察到 Hystrix 将所有来自命令的异常视为断路目的的失败。它包括从 command 运行 () 方法抛出并由 Hystrix 本身创建的异常,例如HystrixTimeoutException.
但是我从 运行() 方法中抛出业务异常,这表示服务响应有效错误,必须进一步处理。
使用来自 SpringWS 的 WebServiceTemplate 时,此类异常的一个示例是 WebServiceFaultException。
所以我不需要那些特定的异常来触发电路。
如何实现这种行为?
有一种明显的方法可以将业务异常包装到持有者对象中,从 运行() 方法返回它,然后将其解包回 Exception 并重新抛出。但是想知道有没有更简洁的方法
有以下可用的解决方案。
Return 异常而不是抛出
最直接和肮脏的方法。这看起来有点古怪,因为你必须删除 Object
的命令并且有很多类型转换。
Observable<BusinessResponse> observable = new HystrixCommand<Object>() {
@Override
protected Object run() throws Exception {
try {
return doStuff(...);
} catch (BusinessException e) {
return e; // so Hystrix won't treat it as a failure
}
}
})
.observe()
.flatMap(new Func1<Object, Observable<BusinessResponse>>() {
@Override
public Observable<BusinessResponse> call(Object o) {
if (o instanceof BusinessException) {
return Observable.error((BusinessException)o);
} else {
return Observable.just((BusinessResponse)o);
}
}
});
使用 holder 对象来保存结果和异常
此方法需要引入额外的支架 class(也可以单独用于其他目的)。
class ResultHolder<T, E extends Exception> {
private T result;
private E exception;
public ResultHolder(T result) {
this.result = result;
}
public ResultHolder(E exception) {
if (exception == null) {
throw new IllegalArgumentException("exception can not be null");
}
this.exception = exception;
}
public T get() throws E {
if (exception != null) {
throw exception;
} else {
return result;
}
}
public Observable<T> observe() {
if (exception != null) {
return Observable.error(exception);
} else {
return Observable.just(result);
}
}
@SuppressWarnings("unchecked")
public static <T, E extends Exception> ResultHolder<T, E> wrap(BusinessMethod<T, E> method) {
try {
return new ResultHolder<>(method.call());
} catch (Exception e) {
return new ResultHolder<>((E)e);
}
}
public static <T, E extends Exception> Observable<T> unwrap(ResultHolder<T, E> holder) {
return holder.observe();
}
interface BusinessMethod<T, E extends Exception> {
T call() throws E;
}
}
现在使用它的代码看起来更简洁了,唯一的缺点可能是大量的泛型。此外,这种方法在 Java 8 中是最好的,其中 lambda 和方法引用可用,否则它看起来很笨重。
new HystrixCommand<ResultHolder<BusinessResponse, BusinessException>>() {
@Override
protected ResultHolder<BusinessResponse, BusinessException> run() throws Exception {
return ResultHolder.wrap(() -> doStuff(...));
}
}
.observe()
.flatMap(ResultHolder::unwrap);
使用 HystrixBadRequestException
HystrixBadRequestException
是一种特殊的异常,在断路器和指标方面不会算作失败。如 documentation 所示:
Unlike all other exceptions thrown by a HystrixCommand this will not
trigger fallback, not count against failure metrics and thus not
trigger the circuit breaker.
HystrixBadRequestException
的实例不是由 Hystrix 本身创建的,因此将其用作业务异常的包装器是安全的。但是,原来的业务异常还是需要解包。
new HystrixCommand<BusinessResponse>() {
@Override
protected BusinessResponse run() throws Exception {
try {
return doStuff(...);
} catch (BusinessException e) {
throw new HystrixBadRequestException("Business exception occurred", e);
}
}
}
.observe()
.onErrorResumeNext(e -> {
if (e instanceof HystrixBadRequestException) {
e = e.getCause(); // Unwrap original BusinessException
}
return Observable.error(e);
})
我观察到 Hystrix 将所有来自命令的异常视为断路目的的失败。它包括从 command 运行 () 方法抛出并由 Hystrix 本身创建的异常,例如HystrixTimeoutException.
但是我从 运行() 方法中抛出业务异常,这表示服务响应有效错误,必须进一步处理。 使用来自 SpringWS 的 WebServiceTemplate 时,此类异常的一个示例是 WebServiceFaultException。
所以我不需要那些特定的异常来触发电路。 如何实现这种行为?
有一种明显的方法可以将业务异常包装到持有者对象中,从 运行() 方法返回它,然后将其解包回 Exception 并重新抛出。但是想知道有没有更简洁的方法
有以下可用的解决方案。
Return 异常而不是抛出
最直接和肮脏的方法。这看起来有点古怪,因为你必须删除 Object
的命令并且有很多类型转换。
Observable<BusinessResponse> observable = new HystrixCommand<Object>() {
@Override
protected Object run() throws Exception {
try {
return doStuff(...);
} catch (BusinessException e) {
return e; // so Hystrix won't treat it as a failure
}
}
})
.observe()
.flatMap(new Func1<Object, Observable<BusinessResponse>>() {
@Override
public Observable<BusinessResponse> call(Object o) {
if (o instanceof BusinessException) {
return Observable.error((BusinessException)o);
} else {
return Observable.just((BusinessResponse)o);
}
}
});
使用 holder 对象来保存结果和异常
此方法需要引入额外的支架 class(也可以单独用于其他目的)。
class ResultHolder<T, E extends Exception> {
private T result;
private E exception;
public ResultHolder(T result) {
this.result = result;
}
public ResultHolder(E exception) {
if (exception == null) {
throw new IllegalArgumentException("exception can not be null");
}
this.exception = exception;
}
public T get() throws E {
if (exception != null) {
throw exception;
} else {
return result;
}
}
public Observable<T> observe() {
if (exception != null) {
return Observable.error(exception);
} else {
return Observable.just(result);
}
}
@SuppressWarnings("unchecked")
public static <T, E extends Exception> ResultHolder<T, E> wrap(BusinessMethod<T, E> method) {
try {
return new ResultHolder<>(method.call());
} catch (Exception e) {
return new ResultHolder<>((E)e);
}
}
public static <T, E extends Exception> Observable<T> unwrap(ResultHolder<T, E> holder) {
return holder.observe();
}
interface BusinessMethod<T, E extends Exception> {
T call() throws E;
}
}
现在使用它的代码看起来更简洁了,唯一的缺点可能是大量的泛型。此外,这种方法在 Java 8 中是最好的,其中 lambda 和方法引用可用,否则它看起来很笨重。
new HystrixCommand<ResultHolder<BusinessResponse, BusinessException>>() {
@Override
protected ResultHolder<BusinessResponse, BusinessException> run() throws Exception {
return ResultHolder.wrap(() -> doStuff(...));
}
}
.observe()
.flatMap(ResultHolder::unwrap);
使用 HystrixBadRequestException
HystrixBadRequestException
是一种特殊的异常,在断路器和指标方面不会算作失败。如 documentation 所示:
Unlike all other exceptions thrown by a HystrixCommand this will not trigger fallback, not count against failure metrics and thus not trigger the circuit breaker.
HystrixBadRequestException
的实例不是由 Hystrix 本身创建的,因此将其用作业务异常的包装器是安全的。但是,原来的业务异常还是需要解包。
new HystrixCommand<BusinessResponse>() {
@Override
protected BusinessResponse run() throws Exception {
try {
return doStuff(...);
} catch (BusinessException e) {
throw new HystrixBadRequestException("Business exception occurred", e);
}
}
}
.observe()
.onErrorResumeNext(e -> {
if (e instanceof HystrixBadRequestException) {
e = e.getCause(); // Unwrap original BusinessException
}
return Observable.error(e);
})