vlambda博客
学习文章列表

hystrix进阶-注解hystrix-javanica使用

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

第一步:引入依赖

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

第二步:配置切面

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

@Configuration@EnableAspectJAutoProxy//开启aoppublic 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.RuntimeExceptionUser{id='Joshua', name='hystrix-javanina-fallback'}

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


异步调用

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

@HystrixCommandpublic 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。