vlambda博客
学习文章列表

微服务弹性框架hystrix-javanica详解(上)

Java语言相比其他语言有一些比较great的优点,那就是反射(refleaction)和注解(annotation)。


几乎所有的流行框架比如Spring, Hibernate, myBatis等等,都最大化的使用了这两个特性。


于是Hystrix也想通过引入注解来改善Hystrix的发展。 目前使用Hystrix涉及编写大量的代码,这是快速发展的障碍。 你可能花了很多时间编写Hystrix命令。 Javanica项目的想法是通过引入支持注解让你更容易地使用Hystrix。在传统的使用Hystrix时,你需要编写大量的代码,这显然对开发者并不友好,也会制约Hystrix未来的发展。这种模式下,你需要花很长时间编写一些Hystrix commands。Javanica项目的想法就是想通过引入annotation让你更容易地使用Hystrix。


首先为了让你可以使用hystrix-javanica,你需要在你的项目中加入hystrix-javanica的依赖。

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-javanica</artifactId>
    <version>x.y.z</version>
</dependency>


为了实现AOP的功能,如果你的项目中已经使用了AspectJ,那么你需要在aop.xml中添加hystrix的切面,像下面这样:


<aspects>
        ...
        <aspect name="com.netflix.hystrix.contrib.javanica.aop.
aspectj.HystrixCommandAspect"
/> ... </aspects>


更多AspectJ的配置你可以点击这里here


如果你使用Spring AOP,那么你需要通过使用Spring AOP的namespace来添加指定的配置,这样让Spring能够去管理你的切面,那些你使用写的AspectJ切面,你需要像下面这样声明HystrixCommandAspect作为Spring的bean:


<aop:aspectj-autoproxy/>
    <bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"></bean>


如果你使用Spring的代码配置方式的话,则像下面这样:

@Configuration
public
class HystrixConfiguration {
 @Bean public HystrixCommandAspect hystrixAspect() {
      return new HystrixCommandAspect(); } }


无论你使用哪种方式来创建proxy,javanica都可以和JDK以及CGLIB proxy配合得很好。如果你使用其他的aop框架来创建代理的话,支持AspectJ的那种,或者其他的(例如Javassist),那么让我们知道你使用的是什么lib,我们将尽量在不久的将来添加对这个库的支持。(ps:人性化)


More about Spring AOP + AspectJ read here

Aspect weaving 切面编织

Javanica支持两种编织模式:编译和运行时。 

1、CTW。 要使用CTW模式,您需要使用特定的jar版本:hystrix-javanica-ctw-X.Y.Z。 这个jar是用使用AJC编译器编译的方面组装的。 如果你尝试靠传统的hystrix-javanica-X.Y.Z jar包来使用CTW,那么你会在运行时从iajc构建得到NoSuchMethodError aspectOf()。 此外,您需要使用java属性启动您的应用程序:-DWeavingMode = compile。 注意:Javanica依赖于aspectj库并使用aspectj的内部功能,这些功能不作为开放API的一部分提供,这样可以确保它随着版本迭代。 Javanica在最新的aspectj版本1.8.7下通过了测试。 如果你更新了aspectj版本,并发现了什么问题,那么请不要犹豫,发一个issue过来,或者提交你的贡献吧。

2、RTW 模式。你可以使用传统的hystrix-javanica-X.Y.Z就可以了。

3、LTM 模式。这种模式没有被测试,但应该能够跑通。


如何使用

Hystrix command

Synchronous Execution 同步执行

如果你想要以同步方式运行方法作为Hystrix command的话,您需要使用@HystrixCommand这个annotation来标记方法。

public class UserService {...
    @HystrixCommand
    public User getUserById(String id) {        
         return userResource.getUserById(id); } }...


在上面的例子中,getUserById方法将会在一个新的Hystrix命令中被同步的处理。在这种情况下,默认的command key就是这个命令方法的名字,也就是getUserById,默认的group  key的名字也就是该方法所在的类的名字: UserService。如果你想修改这些,那么你可以使用必要的属性来修改:

   

@HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand")    
public User getUserById(String id) {
      return userResource.getUserById(id); }


要设置threadPoolKey,那么你可以使用 @HystrixCommand#threadPoolKey()

Asynchronous Execution 异步执行

如果你想异步执行,那么你的方法要返回一个 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被订阅时才开始执行命令。


一个是eagerly,一个是lazily。


为了控制这种行为和两种模式之间的切换,@ HystrixCommand提供了一个名为observableExecutionMode的特定参数。


@HystrixCommand(observableExecutionMode = EAGER)表示observe()方法应该用于执行observable命令


@HystrixCommand(observableExecutionMode = LAZY)表示toObservable()应该用于执行observable命令


NOTE: 默认使用的就是EAGER 模式

Fallback

可以通过在@HystrixCommand中声明fallback方法的名称来实现优雅降级,如下所示:

    @HystrixCommand(fallbackMethod = "defaultUser")    
public User getUserById(String id) {
return userResource.getUserById(id); }

private User defaultUser(String id) {
return new User("def", "def"); }

需要注意的是Hystrix命令和fallback方法要放在同一个类下,并且要有相同的签名。(也就是要有同样的参数和返回类型)

Fallback方法可以具有任何访问修饰符,也就是不限制是public还是private还是protected。 方法defaultUser将用于在发生任何错误的情况下处理回退逻辑。


如果你需要运行fallback方法defaultUser作为一个单独的Hystrix命令方法,那么你需要使用HystrixCommand注解来标注它,如下所示:


  @HystrixCommand(fallbackMethod = "defaultUser")    
public User getUserById(String id) {
return userResource.getUserById(id); }

@HystrixCommand private User defaultUser(String id) {
return new User(); }


如果一个fallback(回退)方法被标记了@HystrixCommand,那么这个fallback方法(defaultUser)也可以有自己的fallback方法,像下面这样:

   

    @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命令方法抛出的异常。


Async/Sync fallback.异步/同步fallback


一个fallback可以是异步的或同步的,很多情况下这依赖于命令执行的类型,下面就列出所有可能的用法:


支持组合:


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 command.


This case isn't supported because in the essence a caller does not get a future buy calling getUserById and future is provided by fallback isn't available for a caller anyway, thus execution of a command forces to complete fallbackAsync before a caller gets a result, having said it turns out there is no benefits of async fallback execution. But it can be convenient if a fallback is used for both sync and async commands, if you see this case is very helpful and will be nice to have then create issue to add support for this case.

 

       @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"); } }; }

case 2: sync command, async fallback.同步command,异步fallback。这种组合不支持的原因和上面是一样的。

       

@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"); } }; }


Same restrictions are imposed on using observable feature in javanica.


Error Propagation错误传递

@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的unwrap 禁用掉,也就是所有未忽略的异常都作为HystrixRuntimeException来抛出:


       @HystrixCommand(
ignoreExceptions = {BadRequestException.class},
raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION})
public User getUserById(String id) {
return userResource.getUserById(id); }

Note: 如果command有一个fallback方法,则只有触发fallback逻辑的第一个异常将会被传播到调用者。


例子:

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()) }
}


下一集我们会说有关请求缓存和配置的内容,敬请期待。