微服务弹性框架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())
    }
}

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

原文发布于微信公众号 - ImportSource(importsource)

原文发表时间:2017-01-08

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Lambda

编程规范

领域层–编码规范 2018年4月4日14:10:38 Controller层编写规范 controller层只是负责从service层获得数据,对外暴露API接...

3646
来自专栏后端沉思录

mybatis拦截器分表

mybatis提供了拦截器插件用来处理被拦截的方法的某些逻辑.下面会通过创建8张表,当用户注册时,根据对手机号取余入不同的表.

7083
来自专栏为数不多的Android技巧

Android 插件化原理解析——Hook机制之AMS&PMS

在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook;插件框架通过AOP实现了插件使用和开发的透明性。在讲述Dr...

1311
来自专栏JAVA后端开发

JAVA实现编写平台代码生成器

[项目中经常写CRUD,但实际这些工作,我觉得如果有一个完整的代码规范,完全可以自动生成,加快开发效率. 代码生成器技术原理不复杂,一般就是写好一个模板生成一...

9002
来自专栏jeremy的技术点滴

mybatis-generator使用备忘

3884
来自专栏IT笔记

SpringBoot开发案例之整合mongoDB

? mongodb.jpg 开始前,建议大家去了解以下文章,当然不看也没问题: MongoDB从入门到“精通”之简介和如何安装 MongoDB从入门到“精通”...

5638
来自专栏ml

HDUOJ-------2149Public Sale

Public Sale Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 ...

2958
来自专栏纯洁的微笑

springboot(十八):使用Spring Boot集成FastDFS

上篇文章介绍了《如何使用Spring Boot上传文件》,这篇文章我们介绍如何使用Spring Boot将文件上传到分布式文件系统FastDFS中。 这个项目会...

4964
来自专栏DT乱“码”

JSP+ajax+springMVC+MayBatis处理excel上传导入

jsp <div class="subtext1"> <div cla...

2959
来自专栏猿天地

Spring Cloud Gateway 结合配置中心限流

上篇文章《Spring Cloud Gateway 限流操作》我讲过复杂的限流场景可以通过扩展RedisRateLimiter来实现自己的限流策略。

3943

扫码关注云+社区

领取腾讯云代金券