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吗?
//开启aop
public 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-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即可:
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
"fallback") (defaultFallback =
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而不是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 {
//使用CacheKey
public User getUserById( String id) {
return new User(id, "getUserById");
}
//cacheKeyMethod
public 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);
//首先得初始化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来设置线程池的配置项,比如:
commandProperties = {
@HystrixProperty(name = , value = )
},
threadPoolProperties = {
,
,
,
,
,
}
)
public User getUserById(String id) {
}
还可以在类上添加@DefaultProperties注解来添加默认的配置项,但是优先级比@HystrixCommand要低。
请求collapser
比原生的api方式好用太多了,比如:
public class UserService {
"getUserByIds") (batchMethod =
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。