Hystrix注解使用(非Spring工程)

losetowin 发布于:2018-7-19 15:50 分类:Java  有 223 人浏览,获得评论 0 条 标签: AspectJ Hystrix 

本文地址:https://www.dutycode.com/hystrix_zhujie_fei_spring.html
除非注明,文章均为 www.dutycode.com 原创,欢迎转载!转载请注明本文地址,谢谢。
关于Hystrix

Netflix公司开源的一个工具,提供了降级,熔断,隔离等功能,可以使得分布式系统有更好的容错处理能力。具体可以参考Hystrix官方的说明文档:https://github.com/Netflix/Hystrix/blob/master/README.md


为什么使用注解及遇到的问题

按照官方文档,使用Hystrix比较简单的方式是继承HystrixCommand,比如下面这种:
public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

    public CommandHelloWorld(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        return "Hello " + name + "!";
    }
}

//调用

String s = new CommandHelloWorld("Bob").execute();
Future<String> s = new CommandHelloWorld("Bob").queue();
Observable<String> s = new CommandHelloWorld("Bob").observe();

但这样的开发方式意味着需要改变原有的系统代码,因为一旦需要使用Hystrix功能,就需要继承HystrixCommand,这样在老系统改造过程中,成本比较大。
但注解的方式,就相对友好一些,代码侵入和耦合低,只需要在需要使用Hystrix功能的方法上添加相应的注解即可。

官方也提供了注解的解决方案,叫做hystrix-javanica,很贴心是不?
具体官方文档地址:https://github.com/Netflix/Hystrix/blob/master/hystrix-contrib/hystrix-javanica/README.md

官方文档中提供了spring的接入方式如下:
<aop:aspectj-autoproxy/>
    <bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"></bean>

并且提供了一种使用AspectJ的接入方式,在aop.xml中添加如下配置即可:
<aspects>
        ...
        ...
</aspects>
注:AspectJ的方式,是使用了LTW(load-time weaving)的模式

但是,不太友好的是,Hystrix-javanica对于AspectJ的LTW模式,并没有完全测试通过,所以使用上可能存在BUG。

hystrix-javanica对CTW和RTW的支持相对较好,RTW模式,在Spring中比较常见,如果使用AspectJ的话,就只能考虑CTW和LTW了。 因为AspectJ只支持CTW和LTW,是语言级别的AOP实现。 

备注:在Java语言中,从织入切面的方式来看,分成了 编译时织入、类加载时织入、运行时织入。

好巧不巧,我们工程没有使用Spring,所以就用不了Spring的配置方式。 所以剩下我们能用的只有AspectJ的方式。
然而,hystrix-javanica对LTW没有测试完全,所以我们可选的就只有CTW的模式了。 

官方有关于CTW模式的使用说明
To use CTW mode you need to use specific jar version: hystrix-javanica-ctw-X.Y.Z . This jar is assembled with aspects compiled with using AJC compiler. If you will try to use regular hystrix-javanica-X.Y.Z with CTW then you get NoSuchMethodError aspectOf() at runtime from building with iajc. Also, you need to start your app with using java property: -DWeavingMode=compileNOTE: Javanica depends on aspectj library and uses internal features of aspectj and these features aren't provided as a part of open API thus it can change from version to version. Javanica tested with latest aspectj version 1.8.7. If you updated aspectj version and noticed any issues then please don't hestitate to create new issue or contribute

大体意思就是,支持CTW,但是需要使用特殊的版本 hystrix-javanica-ctw-X.Y.Z 。这个版本的jar包是被AJC编译器编译过的。
问题在于,maven仓库中并没有这个包。

关于hystrix-javanica-ctw的这个包,在Hystrix-javanica的Issue中也有很多人问,包括项目的开发者也回复了,这个包是需要经过本地编译之后的,并没有提交到maven仓库
那这也就意味着,如果需要引用这个包,那就得先本地下载一份源代码,然后自行编译。并且,编译之后的可能还不能提交到内部仓库中,可能存在不适用的情况。 那这样就太费劲了,因为要使用他的功能,却要在项目中引入很多额外代码。 复杂化了使用方式。 

为啥不能直接编译一个上传到maven仓库?
    个人理解是,Ajc编译的时候需要通过@AspectJ的注解来将类定义成切面类,然后将代码进行编译处理。但正常编译时,如果没有引用是不会去扫描jar包中的@AspectJ的,所以也就意味着,即使我们在工程中使用了@HystrixCommand注解,依旧不能被Ajc编译器认为是切面类,也就不会被编译。所以,虽然编译后的ctw包有了切面,但是在工程中,却没办法将原有代码做上切面,因为ajc不认为@HystrixCommand是个切面。 
    
    所以在Issue中,开发者也提到了一种办法,不需要将整个hystrix-javanica工程都放到本地项目中,只需要将切面类放到本地工程中,然后使用Ajc编译即可。 切面类是:
com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect
    关于开发者对于这个问题的讨论的Issue地址:
https://github.com/Netflix/Hystrix/issues/907#issuecomment-147584253
    方便查看,我把关键信息拷贝过来了
@tol I've finished working on support for compile time weaving, but haven't created PL yet, because suddenly I recognized that you cannot you use CTW if an aspect is located in jar and this aspect wasn't compiled using ajc, otherwise you'll get MethodNotFoundException: aspectOf(). If you want to use CTW then you need to copy HystrixCacheAspect and HystrixCommandAspect from javanica source code into your project and compile javanica aspects+your source code files using ajc. If you are ok with it then I'll create PL. BTW, why you don't use LTW, it has same performance I guess. LTW helps to solve this problem but requires javaagent.
    
    当然,拷贝HystrixCommandAspect和HystrixCacheAspect到项目里面,依旧是个比较麻烦的事情,这样意味着每个接入方都需要拷贝一份,后期升级维护成本高。

    好在还有解决办法
    因为项目使用的是maven,maven又支持插件,所以我们发现有个插件能解决这个问题。具体的pom配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dutycode.hystrix</groupId>
    <artifactId>plugmodule</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>1.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.2</version>
        </dependency>

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


    </dependencies>
    <build>
        <plugins>
            <plugin>
                <!--编译时候跳过test-->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.7</version>
                <configuration>
                    <complianceLevel>1.8</complianceLevel>
                    <source>1.6</source>
                    <target>1.6</target>
                    <showWeaveInfo>true</showWeaveInfo>
                    <verbose>true</verbose>
                    <Xlint>ignore</Xlint>
                    <encoding>UTF-8</encoding>
                    <weaveDependencies>
                        <weaveDependency>
                            <groupId>com.netflix.hystrix</groupId>
                            <artifactId>hystrix-javanica</artifactId>
                        </weaveDependency>
                    </weaveDependencies>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <!-- use this goal to weave all your main classes -->
                            <goal>compile</goal>
                            <!-- use this goal to weave all your test classes -->
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>


关键点在于 weaveDependencies这个配置,他可以让maven在打包的时候,主动去找指定jar包中的切面类,然后做编织。 

按照这样的配置下来,项目就能正常运行了。 一个简单的Demo如下:
public class HelloWorld {


    @HystrixCommand(fallbackMethod = "defaultFallback")
    public void sayHello() {
        System.out.println("say Hello");

        throw new RuntimeException("fallback1 failed");
    }


    public void defaultFallback() {
        System.out.println("defaultFallback");
    }


    public static void main(String[] args) {

        HelloWorld helloWorld = new HelloWorld();
        helloWorld.sayHello();
    }

}

执行结果如下:
020600A2-9618-4E56-B8B5-9664929A418C.png
结果符合预期。


另外:
特别强调一点,一定要好好看官方文档说明,在调试过程中,出现了一次问题,发现不管怎么编译,怎么调整,最后执行的时候都是直接执行到了fallback方法中,没有走该有的方法。在上面的例子中,执行结果一直没有say Hello, 而是直接输出了defalutFallback。
这是完全错误的,因为压根就没有异常降级,他是把所有业务都降级了。 
然后自己吭哧吭哧看了半天的源代码,才发现在HystrixCommandAspect 中,会判断编织类型,如果编织类型为编译期编织的话,就会使用AjcMethod,也就是Ajc编译器生成的方法。但是,系统默认编织类型是运行时编织。所以,也就意味着,根本走不到切面的方法。 于是根据源代码,知道了需要在jvm参数上增加-DWeavingMode=complie的参数。
但是,官方文档中早就写了啊,如果使用CTW模式,要在参数上加-DWeavingMode=compile。本来1分钟可以解决的事情,白白花了接近半天。得不偿失。 

补充:
关于Maven AspectJ的一个比较好的文章 http://www.baeldung.com/aspectj  

版权所有:《攀爬蜗牛》 => 《Hystrix注解使用(非Spring工程)
本文地址:https://www.dutycode.com/hystrix_zhujie_fei_spring.html
除非注明,文章均为 《攀爬蜗牛》 原创,欢迎转载!转载请注明本文地址,谢谢。