美文网首页
hystrix进阶-注解hystrix-javanica使用

hystrix进阶-注解hystrix-javanica使用

作者: Joshua1919 | 来源:发表于2020-08-16 15:14 被阅读0次

    前一篇我们讲了hystrix原生api的使用,用起来还是比较复杂的,如果想让一个方法支持fallback还得去继承HystrixCommand,跟业务完全耦合到一起,对业务的侵入性太大了,显然不利于hystrix的使用,因此hystrix-javanica出现了,可以让应用以注解的方式更方便的来使用hystrix。

    先看个demo

    第一步:引入依赖

    <dependencies>
      <dependency>
      <groupId>com.netflix.hystrix</groupId>
      <artifactId>hystrix-javanica</artifactId>
      <version>1.5.18</version>
    </dependency>
    

    第二步:配置切面

    要实现方法的fallback、断路等功能最合适的方式不就是使用aop吗?

    @Configuration
    @EnableAspectJAutoProxy//开启aop
    public class AppConfig {
        @Bean//配置切面
        public HystrixCommandAspect hystrixAspect() {
            return new HystrixCommandAspect();
        }
        @Bean 
        UserService userService(){
            return new UserService();
        }
    }
    

    首先要添加EnableAspectJAutoProxy注解开启aop,然后配置切面HystrixCommandAspect。

    第三步:在业务方法上添加HystrixCommand注解

    
    public class UserService {
        @HystrixCommand(fallbackMethod = "fallback")
        public User getUserById(String id) {
            throw new RuntimeException("hystrix-javanina-fallback");
        }
        public User fallback(String id, Throwable e){
            System.out.println(e.getClass().getName());
            return new User(id, e.getMessage());
        }
    }
    

    测试一下:

    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = ctx.getBean(UserService.class);
        System.out.println(userService.getUserById("Joshua"));
    }
    //执行结果
    java.lang.RuntimeException: hystrix-javanina-fallback
      at com.github.xjs.hystrix.demo1.UserService.getUserById(UserService.java:10)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
      at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
      at java.lang.reflect.Method.invoke(Method.java:498)
      。。。。
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
      at java.lang.Thread.run(Thread.java:748)
    java.lang.RuntimeException
    User{id='Joshua', name='hystrix-javanina-fallback'}
    

    异常首先是打印输出,然后执行了fallback,程序正常返回。同时,在fallback里面是可以获取到原始的异常信息的,这比原生的api更强大了。

    异步调用

    以上的demo是同步执行方法,异步执行的话只需要返回Future即可:

    
    @HystrixCommand
    public Future<User> getUserByName(String name) {
      return new AsyncResult<User>() {
        @Override
        public User invoke() {
          return new User("id", name);
        }
      };
    }
    

    fallback降级

    上面的代码已经演示了如何配置fallback方法,需要注意的是:
    (1)fallback方法必须得跟业务方法在同一个类中
    (2)fallback方法必须跟业务方法有相同的方法签名,访问修饰符可以随意,但是可以添加一个异常参数用来捕获业务方法中的异常。
    (3)fallback方法本身还可以继续有自己的fallback,比如:

    public class UserService {
        @HystrixCommand(fallbackMethod = "defaultUser")
        public User getUserById(String id) {
            throw new RuntimeException("getUserById");
        }
        @HystrixCommand(fallbackMethod = "defaultUserSecond")
        public User defaultUser(String id, Throwable t){
            System.out.println("exception message:" + t.getMessage());
            throw new RuntimeException("defaultUser");
        }
        public User defaultUserSecond(String id, Throwable t){
            System.out.println("exception message:" + t.getMessage());
            return new User();
        }
    }
    

    如果fallback方法上添加了异常参数,那么它只能获取它的上一个业务方法中抛出的异常。

    (4)fallback也可以异步来执行。
    因为业务方法本身也可以异步,因此二者的有效组合可以是:
    同步方法,同步fallback
    异步方法, 同步fallback
    异步方法, 异步fallback
    但是不支持异步方法,同步fallback,因为只有先执行方法才会执行fallback。

    默认的fallback

    可以给类里面所有的业务方法定义一个默认的fallback:

    
    //默认的fallback
    @DefaultProperties(defaultFallback = "fallback")
    public class UserService {
        @HystrixCommand//无参
        public User getRandomUser(){
            throw new RuntimeException("getRandomUser");
        }
        @HystrixCommand//long参数
        public User getUserById(Long id) {
            throw new RuntimeException("getUserById");
        }
        @HystrixCommand//String参数
        public User getUserByName(String name){
            throw new RuntimeException("getUserByName");
        }
        //默认的fallback不能有参数,
        public User fallback(Throwable t){
            System.out.println("exception message:" + t.getMessage());
            return new User();
        }
    }
    

    需要说明的是HystrixCommand上也可以定义默认的fallback方法,如果同时在类上和业务方法上都定义了默认的fallback,那么方法上的优先级更高,此外,不管是类上还是方法上的默认fallback都是不可以有业务参数的,最多只可以有异常参数。

    异常的传播

    如果某种特定的异常不想触发fallback,可以设置ignoreExceptions来忽略,比如:

    public class UserService {
        @HystrixCommand(fallbackMethod = "fallback", ignoreExceptions = BizException.class)
        public User getUserById(Long id) {
            throw new BizException();
        }
        public User fallback(Long id, Throwable t){
            System.out.println("id:"+id+",exception message:" + t.getMessage());
            return new User(""+id,"fallback");
        }
    }
    

    此时,上面的方法是不会进入到fallback()而是会把BizException异常直接抛出。

    需要说明的是,调用者拿到的异常是业务方法抛出的原始异常,可以通过raiseHystrixExceptions把原始异常统一包装成HystrixRuntimeException,如下:

    
    @HystrixCommand(raiseHystrixExceptions = HystrixException.RUNTIME_EXCEPTION)
      public User getUserByName(String name) {
          throw new BizException();
      }
     public static void main(String[] args)throws Exception {
      try{
          System.out.println(userService.getUserByName("Joshua"));
      }catch(Exception e){
          //这里拿到的就是HystrixRuntimeException而不是BizException
          System.out.println(e.getClass().getName());
          //这个才是BizException
          System.out.println(e.getCause().getClass().getName());
      }
    }
    

    但是,无论如何,fallback里面获取到的异常始终都是业务方法抛出的最原始的异常。

    请求缓存

    @CacheResult:说明需要把结果缓存起来
    @CacheKey:缓存的key
    @CacheRemove:清理缓存
    @CacheResult和@CacheRemove本身有一个cacheKeyMethod属性,优先级更高。如果既没有定义CacheKey,也没有定义cacheKeyMethod,那么就会使用所有的请求参数当成缓存的key。

    
    public class UserService {
        @CacheResult
        @HystrixCommand(commandKey = "getUserById")
        //使用CacheKey
        public User getUserById(@CacheKey String id) {
            return new User(id, "getUserById");
        }
        //cacheKeyMethod
        @CacheResult(cacheKeyMethod = "getUserByNameCacheKey")
        @HystrixCommand
        public User getUserByName(String name) {
            return new User("id" ,name);
        }
        private String getUserByNameCacheKey(String name) {
            return name;
        }
        //清理缓存
        @CacheRemove(commandKey="getUserById" )
        @HystrixCommand
        public void update(@CacheKey("id") User user) {
            return;
        }
    }
    

    使用之前首先要初始化context:

    
    public static void main(String[] args)throws Exception {
      AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
      UserService userService = ctx.getBean(UserService.class);
      //首先得初始化context
      HystrixRequestContext context = HystrixRequestContext.initializeContext();
      try{
        User u1 = userService.getUserById("id");
        User u2 = userService.getUserById("id");
        //true
        System.out.println(u1 == u2);
        //updat会清理缓存
        userService.update(u1);
        User u3 = userService.getUserById("id");
        User u4 = userService.getUserById("id");
        //false
        System.out.println(u2 == u3);
        //true
        System.out.println(u3 == u4);
      }finally{
        context.shutdown();
      }
    }
    

    要注意的是,@CacheRemove里面的commandKey要跟想要清理的command的command key一样,同时缓存的key必须要保持一致才可以清理掉command的缓存。

    配置Command的属性

    通过@HystrixCommand的commandProperties来设置配置项:

    
    @HystrixCommand(commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
            })
    public User getUserById(String id) {
    }
    

    以上代码就等价于:

    ConfigurationManager.getConfigInstance().setProperty(
    "hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500");
    

    默认的command的名字就是方法的名字。
    通过@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) {
    }
    

    还可以在类上添加@DefaultProperties注解来添加默认的配置项,但是优先级比@HystrixCommand要低。

    请求collapser

    比原生的api方式好用太多了,比如:

    public class UserService {
        @HystrixCollapser(batchMethod = "getUserByIds")
        public User getUserById(String id) {
            return null;
        }
        @HystrixCommand
        public List<User> getUserByIds(List<String> ids) {
            List<User> users = new ArrayList<User>();
            for (String id : ids) {
                users.add(new User(id, "name: " + id));
            }
            return users;
        }
    }
    

    测试下:

    
    public static void main(String[] args)throws Exception {
      AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
      HystrixRequestContext context = HystrixRequestContext.initializeContext();
      try{
        UserService userService = ctx.getBean(UserService.class);
        System.out.println(userService.getUserById("1"));
        System.out.println(userService.getUserById("2"));
        System.out.println(userService.getUserById("3"));
      }finally{
        context.close();
      }
    }
    

    需要注意的是:
    (1)@HystrixCollapser的入参必须不能为空
    (2)@HystrixCommand的入参是@HystrixCollapser的入参的List,
    (3)@HystrixCommand的返回是@HystrixCollapser的返回的List
    (4)@HystrixCommand的返回值的个数必须要等于入参的个数

    参考代码下载:https://github.com/xjs1919/enumdemo下面的hystrix-demo/hystrix-javanica。

    欢迎扫码查看更多文章:


    qrcode.jpg

    相关文章

      网友评论

          本文标题:hystrix进阶-注解hystrix-javanica使用

          本文链接:https://www.haomeiwen.com/subject/ddwtjktx.html