hystrix进阶-注解hystrix-javanica使用

2020-08-16  本文已影响0人  Joshua1919

前一篇我们讲了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
上一篇下一篇

猜你喜欢

热点阅读