Java语言比其他语言(如反射和注解)具有很大的优势。所有现代框架,如Spring,Hibernate,myBatis等都力求最大限度地利用这一优势。
在Hystrix中引入注解的想法是改进的明显解决方案。目前使用Hystrix涉及编写大量代码,这是快速开发的障碍。您可能花费大量时间编写Hystrix命令。
通过引入支持注解,Javanica项目使Hystrix更容易使用。
首先,为了使用hystrix-javanica,您需要在项目中添加hystrix-javanica依赖关系。
Maven样例:
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>x.y.z</version>
</dependency>
在项目中实现AOP功能可使用了AspectJ库。如果在您的项目中已经使用AspectJ,那么需要在aop.xml中添加hystrix切面,如下所示:
<aspects>
...
<aspect name="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>
...
</aspects>
关于AspectJ配置的更多信息请读这里
如果在项目中使用Spring AOP,则需要使用Spring AOP命名空间添加特定配置,以使Spring能够管理使用AspectJ编写的切面,并将HystrixCommandAspect声明为Spring bean,如下所示:
<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"></bean>
或者如果您使用的是Spring java配置:
@Configuration
public class HystrixConfiguration {
@Bean
public HystrixCommandAspect hystrixAspect() {
return new HystrixCommandAspect();
}
}
在Spring中使用哪种方法来创建代理并不重要,javanica适用于JDK和CGLIB代理。如果您为aop使用另一个支持AspectJ的框架,并使用其他lib(例如Javassist)创建代理,
那么让我们知道您用于创建代理的lib,我们将在不久的将来尝试添加对该库的支持。
更多关于Spring AOP AspectJ知识请读这里
Javanica支持两种编织模式:编译时和运行时。目前加载时织入没有经过测试,但它应该工作。
要以Hystrix命令同步运行方法,您需要使用@HystrixCommand注解来注释方法:
public class UserService {
...
@HystrixCommand
public User getUserById(String id) {
return userResource.getUserById(id);
}
}
...
在上面的例子中,getUserById
方法将在新的Hystrix命令中同步处理。默认情况下,command key的名称是命令方法名称:getUserById
,
默认group key 名称是被注释方法的类名称:UserService
。当然,您也可以使用@HystrixCommand的属性更改它:
@HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand")
public User getUserById(String id) {
return userResource.getUserById(id);
}
设置threadPoolKey可使用@HystrixCommand#threadPoolKey()
要异步处理Hystrix命令,您应该在命令方法中返回AsyncResult
的实例,如下所示:
@HystrixCommand
public Future<User> getUserByIdAsync(final String id) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return userResource.getUserById(id);
}
};
}
命令方法的返回类型应为Future,表示应该以异步方式执行命令
要想“Reactive Execution”,您应该在命令方法中返回一个Observable的实例,如下例所示:
@HystrixCommand
public Observable<User> getUserById(final String id) {
return Observable.create(new Observable.OnSubscribe<User>() {
@Override
public void call(Subscriber<? super User> observer) {
try {
if (!observer.isUnsubscribed()) {
observer.onNext(new User(id, name + id));
observer.onCompleted();
}
} catch (Exception e) {
observer.onError(e);
}
}
});
}
命令方法的返回类型应该是Observable
。
HystrixObservable接口提供了两种方法:observe()
- 与HystrixCommand#queue()
或HystrixCommand#execute()
行为一样,立即开始执行命令;
toObservable()
- 一旦Observable
被订阅,懒惰地开始执行命令。
为了控制这种行为,并且在两种模式之间切换,@HystrixCommand
提供了名为observableExecutionMode
的特定属性。
@HystrixCommand(observableExecutionMode = EAGER)
表示应该使用observe()
方法执行observable命令;
@HystrixCommand(observableExecutionMode = LAZY)
表示应该使用toObservable()
方法来执行observable命令。
注意:默认情况下使用EAGER
模式
可以通过在@HystrixCommand
中声明`fallbackMethod`来实现正常退化,像下面这样:
@HystrixCommand(fallbackMethod = "defaultUser")
public User getUserById(String id) {
return userResource.getUserById(id);
}
private User defaultUser(String id) {
return new User("def", "def");
}
重要的是要记住,Hystrix命令和回退方法应该放在同一个类中并具有相同的方法签名(执行失败的异常(诱发服务降级的异常)为可选参数)。回退方法可以有任何访问修饰符。
方法defaultUser
将用于在任何错误的情况下处理回退逻辑。如果您需要将fallback methoddefaultUser
作为单独的Hystrix命令运行,那么您需要使用HystrixCommand注释对其进行注释,如下所示:
@HystrixCommand(fallbackMethod = "defaultUser")
public User getUserById(String id) {
return userResource.getUserById(id);
}
@HystrixCommand
private User defaultUser(String id) {
return new User();
}
如果回退方法标记为@HystrixCommand,那么这种回退方法(defaultUser)也可以有自己的回退方法,如下例所示:
@HystrixCommand(fallbackMethod = "defaultUser")
public User getUserById(String id) {
return userResource.getUserById(id);
}
@HystrixCommand(fallbackMethod = "defaultUserSecond")
private User defaultUser(String id) {
return new User();
}
@HystrixCommand
private User defaultUserSecond(String id) {
return new User("def", "def");
}
Javanica提供了在执行fallback
中获取执行异常(导致命令失败抛出的异常)的能力。你可以使用附加参数扩展fallback方法签名,以获取命令抛出的异常。
Javanica通过fallback方法的附加参数来公开执行异常。执行异常是通过调用方法getExecutionException()
在vanilla hystrix中得到的。
示例:
@HystrixCommand(fallbackMethod = "fallback1")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
@HystrixCommand(fallbackMethod = "fallback2")
User fallback1(String id, Throwable e) {
assert "getUserById command failed".equals(e.getMessage());
throw new RuntimeException("fallback1 failed");
}
@HystrixCommand(fallbackMethod = "fallback3")
User fallback2(String id) {
throw new RuntimeException("fallback2 failed");
}
@HystrixCommand(fallbackMethod = "staticFallback")
User fallback3(String id, Throwable e) {
assert "fallback2 failed".equals(e.getMessage());
throw new RuntimeException("fallback3 failed");
}
User staticFallback(String id, Throwable e) {
assert "fallback3 failed".equals(e.getMessage());
return new User("def", "def");
}
// test
@Test
public void test() {
assertEquals("def", getUserById("1").getName());
}
如你所见,附加的Throwable参数不是强制性的,可以省略或指定。fallback可以得到父层方法执行失败的异常,因此fallback3会收到fallback2抛出的异常,而不是getUserById命令中的异常。
回退可以是异步或同步,在某些情况下,它取决于命令执行类型,下面列出了所有可能的使用:
case 1: 同步 command, 同步 fallback
@HystrixCommand(fallbackMethod = "fallback")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
@HystrixCommand
User fallback(String id) {
return new User("def", "def");
}
case 2: 异步 command, 同步 fallback
@HystrixCommand(fallbackMethod = "fallback")
Future<User> getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
@HystrixCommand
User fallback(String id) {
return new User("def", "def");
}
case 3: 异步 command, 异步 fallback
@HystrixCommand(fallbackMethod = "fallbackAsync")
Future<User> getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
@HystrixCommand
Future<User> fallbackAsync(String id) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return new User("def", "def");
}
};
}
case 1: 同步 command, 异步 fallback。这种情况是不支持的,因为在本质上,一个调用者执行getUserById
方法不会得到Future的结果,并且fallback方法提供的Future结果对调用者根本不可用。
因此执行命令会在调用者获得结果之前强制完成fallbackAsync,已经说过,事实证明,async fallback执行没有任何好处。但是,如果同步和异步命令同时使用某个fallback,这可能是很方便,
如果你看到这种情况是非常有帮助的,那么请来创建问题以便我们很好支持。
@HystrixCommand(fallbackMethod = "fallbackAsync")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
@HystrixCommand
Future<User> fallbackAsync(String id) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return new User("def", "def"); // 实际期望获取这个结果,但是被强制执行返回了Future<User>结果,该结果对调用者不可用
}
};
}
case 2: 同步 command, 异步 fallback, 与案例1相同的原因,不支持这种情况。
@HystrixCommand(fallbackMethod = "fallbackAsync")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
Future<User> fallbackAsync(String id) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return new User("def", "def");
}
};
}
在javanica中使用observable功能有相同的限制。
此功能允许为整个类或具体命令定义默认回退。如果您有一批具有完全相同的回退逻辑的命令,您仍然必须为每个命令定义回退方法,因为回退方法应该具有与命令完全相同的签名,请考虑以下代码:
public class Service {
@RequestMapping(value = "/test1")
@HystrixCommand(fallbackMethod = "fallback")
public APIResponse test1(String param1) {
// some codes here
return APIResponse.success("success");
}
@RequestMapping(value = "/test2")
@HystrixCommand(fallbackMethod = "fallback")
public APIResponse test2() {
// some codes here
return APIResponse.success("success");
}
@RequestMapping(value = "/test3")
@HystrixCommand(fallbackMethod = "fallback")
public APIResponse test3(ObjectRequest obj) {
// some codes here
return APIResponse.success("success");
}
private APIResponse fallback(String param1) {
return APIResponse.failed("Server is busy");
}
private APIResponse fallback() {
return APIResponse.failed("Server is busy");
}
private APIResponse fallback(ObjectRequest obj) {
return APIResponse.failed("Server is busy");
}
}
默认回退功能允许采用DRY原则(Don’t Repeat Yourself,不要重复你自己)并摆脱冗余:
@DefaultProperties(defaultFallback = "fallback")
public class Service {
@RequestMapping(value = "/test1")
@HystrixCommand
public APIResponse test1(String param1) {
// some codes here
return APIResponse.success("success");
}
@RequestMapping(value = "/test2")
@HystrixCommand
public APIResponse test2() {
// some codes here
return APIResponse.success("success");
}
@RequestMapping(value = "/test3")
@HystrixCommand
public APIResponse test3(ObjectRequest obj) {
// some codes here
return APIResponse.success("success");
}
private APIResponse fallback() {
return APIResponse.failed("Server is busy");
}
}
默认的回退方法不应该有任何参数,除了可以附加获取执行异常参数,不应该抛出任何异常。以降序优先级列(如果设置1,则不执行2)出如下:
基于此描述,@HystrixCommand具有指定应被忽略的异常类型的能力。
@HystrixCommand(ignoreExceptions = {BadRequestException.class})
public User getUserById(String id) {
return userResource.getUserById(id);
}
如果userResource.getUserById(id);
抛出类型为BadRequestException
的异常,则此异常将被包装在HystrixBadRequestException
中,并重新抛出,而不触发后备逻辑。
你不需要手动执行,javanica会为你做这个。
值得注意的是,默认情况下,一个调用者总是会得到根本原因异常,例如BadRequestException
,而不是HystrixBadRequestException
或HystrixRuntimeException
(除非有执行代码显式抛出这些异常的情况)。
可选地,通过使用raiseHystrixExceptions
,可以禁用HystrixRuntimeException
的拆箱。即所有未被忽略的异常会作为HystrixRuntimeException
的cause出现。
@HystrixCommand(
ignoreExceptions = {BadRequestException.class},
raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION})
public User getUserById(String id) {
return userResource.getUserById(id);
}
注意:如果命令有一个回退,则只有触发回退逻辑的第一个异常将被传播给调用者。例:
class Service {
@HystrixCommand(fallbackMethod = "fallback")
Object command(Object o) throws CommandException {
throw new CommandException();
}
@HystrixCommand
Object fallback(Object o) throws FallbackException {
throw new FallbackException();
}
}
// in client code
{
try {
service.command(null);
} catch (Exception e) {
assert CommandException.class.equals(e.getClass())
}
}
……
命令属性可以使用@HystrixCommand的’commandProperties’设置,如下所示:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
})
public User getUserById(String id) {
return userResource.getUserById(id);
}
Javanica使用Hystrix ConfigurationManager
动态设置属性。对于上面的例子,Javanica幕后执行的动作:
ConfigurationManager.getConfigInstance().setProperty("hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500");
更多关于Hystrix命令属性command和fallback
ThreadPoolProperties可以使用@HystrixCommand的’threadPoolProperties’设置,如下所示:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
})
public User getUserById(String id) {
return userResource.getUserById(id);
}
@DefaultProperties是类(类型)级别注释,允许设置的默认命令属性,有groupKey
,threadPoolKey
,commandProperties
,threadPoolProperties
,
ignoreExceptions
和raiseHystrixExceptions
。
默认情况下,使用此注释指定的属性将在注释类中定义的每个hystrix命令使用,除非命令明确使用相应的@HystrixCommand参数来指定这些属性(覆盖默认行为)。例:
@DefaultProperties(groupKey = "DefaultGroupKey")
class Service {
@HystrixCommand // hystrix command group key is 'DefaultGroupKey'
public Object commandInheritsDefaultProperties() {
return null;
}
@HystrixCommand(groupKey = "SpecificGroupKey") // command overrides default group key
public Object commandOverridesGroupKey() {
return null;
}
}
……
原文链接: hystrix-javanica & 推荐阅读