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吗?
//开启aoppublic class AppConfig {//配置切面public HystrixCommandAspect hystrixAspect() {return new HystrixCommandAspect();}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-fallbackat 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即可:
public Future<User> getUserByName(String name) {return new AsyncResult<User>() {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(defaultFallback = "fallback")public class UserService {//无参public User getRandomUser(){throw new RuntimeException("getRandomUser");}//long参数public User getUserById(Long id) {throw new RuntimeException("getUserById");}//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 {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,如下:
(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而不是BizExceptionSystem.out.println(e.getClass().getName());//这个才是BizExceptionSystem.out.println(e.getCause().getClass().getName());}}
但是,无论如何,fallback里面获取到的异常始终都是业务方法抛出的最原始的异常。
请求缓存
@CacheResult:说明需要把结果缓存起来
@CacheKey:缓存的key
@CacheRemove:清理缓存
@CacheResult和@CacheRemove本身有一个cacheKeyMethod属性,优先级更高。如果既没有定义CacheKey,也没有定义cacheKeyMethod,那么就会使用所有的请求参数当成缓存的key。
public class UserService {//使用CacheKeypublic User getUserById( String id) {return new User(id, "getUserById");}//cacheKeyMethodpublic User getUserByName(String name) {return new User("id" ,name);}private String getUserByNameCacheKey(String name) {return name;}//清理缓存public void update( User user) {return;}}
使用之前首先要初始化context:
public static void main(String[] args)throws Exception {AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);UserService userService = ctx.getBean(UserService.class);//首先得初始化contextHystrixRequestContext context = HystrixRequestContext.initializeContext();try{User u1 = userService.getUserById("id");User u2 = userService.getUserById("id");//trueSystem.out.println(u1 == u2);//updat会清理缓存userService.update(u1);User u3 = userService.getUserById("id");User u4 = userService.getUserById("id");//falseSystem.out.println(u2 == u3);//trueSystem.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来设置线程池的配置项,比如:
commandProperties = {@HystrixProperty(name = , value = )},threadPoolProperties = {,,,,,})public User getUserById(String id) {}
还可以在类上添加@DefaultProperties注解来添加默认的配置项,但是优先级比@HystrixCommand要低。
请求collapser
比原生的api方式好用太多了,比如:
public class UserService {(batchMethod = "getUserByIds")public User getUserById(String id) {return null;}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。
