Hystrix Javanica

hystrix-javanica

Java语言比其他语言(如反射和注解)具有很大的优势。所有现代框架,如Spring,Hibernate,myBatis等都力求最大限度地利用这一优势。
在Hystrix中引入注解的想法是改进的明显解决方案。目前使用Hystrix涉及编写大量代码,这是快速开发的障碍。您可能花费大量时间编写Hystrix命令。
通过引入支持注解,Javanica项目使Hystrix更容易使用。

首先,为了使用hystrix-javanica,您需要在项目中添加hystrix-javanica依赖关系。

Maven样例:

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-javanica</artifactId>
    <version>x.y.z</version>
</dependency>

在项目中实现AOP功能可使用了AspectJ库。如果在您的项目中已经使用AspectJ,那么需要在aop.xml中添加hystrix切面,如下所示:

<aspects>
        ...
        <aspect name="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"/>
        ...
</aspects>

关于AspectJ配置的更多信息请读这里

如果在项目中使用Spring AOP,则需要使用Spring AOP命名空间添加特定配置,以使Spring能够管理使用AspectJ编写的切面,并将HystrixCommandAspect声明为Spring bean,如下所示:

<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect"></bean>

或者如果您使用的是Spring java配置:

@Configuration
public class HystrixConfiguration {

  @Bean
  public HystrixCommandAspect hystrixAspect() {
    return new HystrixCommandAspect();
  }

}

在Spring中使用哪种方法来创建代理并不重要,javanica适用于JDK和CGLIB代理。如果您为aop使用另一个支持AspectJ的框架,并使用其他lib(例如Javassist)创建代理,
那么让我们知道您用于创建代理的lib,我们将在不久的将来尝试添加对该库的支持。

更多关于Spring AOP AspectJ知识请读这里

切面织入

Javanica支持两种编织模式:编译时和运行时。目前加载时织入没有经过测试,但它应该工作。

如何使用

Hystrix命令

同步执行

要以Hystrix命令同步运行方法,您需要使用@HystrixCommand注解来注释方法:

public class UserService {
...
    @HystrixCommand
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }
}
...

在上面的例子中,getUserById方法将在新的Hystrix命令中同步处理。默认情况下,command key的名称是命令方法名称:getUserById
默认group key 名称是被注释方法的类名称:UserService。当然,您也可以使用@HystrixCommand的属性更改它:

    @HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand")
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

设置threadPoolKey可使用@HystrixCommand#threadPoolKey()

异步执行

要异步处理Hystrix命令,您应该在命令方法中返回AsyncResult的实例,如下所示:

    @HystrixCommand
    public Future<User> getUserByIdAsync(final String id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return userResource.getUserById(id);
            }
        };
    }

命令方法的返回类型应为Future,表示应该以异步方式执行命令

响应式执行

要想“Reactive Execution”,您应该在命令方法中返回一个Observable的实例,如下例所示:

    @HystrixCommand
    public Observable<User> getUserById(final String id) {
        return Observable.create(new Observable.OnSubscribe<User>() {

                @Override
                public void call(Subscriber<? super User> observer) {
                    try {
                        if (!observer.isUnsubscribed()) {
                            observer.onNext(new User(id, name + id));
                            observer.onCompleted();
                        }
                    } catch (Exception e) {
                        observer.onError(e);
                    }
                }

            });
    }

命令方法的返回类型应该是Observable

HystrixObservable接口提供了两种方法:observe() - 与HystrixCommand#queue()HystrixCommand#execute()行为一样,立即开始执行命令;
toObservable() - 一旦Observable被订阅,懒惰地开始执行命令。
为了控制这种行为,并且在两种模式之间切换,@HystrixCommand提供了名为observableExecutionMode的特定属性。
@HystrixCommand(observableExecutionMode = EAGER)表示应该使用observe()方法执行observable命令;
@HystrixCommand(observableExecutionMode = LAZY)表示应该使用toObservable()方法来执行observable命令。

注意:默认情况下使用EAGER模式

Fallback

可以通过在@HystrixCommand中声明`fallbackMethod`来实现正常退化,像下面这样:

 @HystrixCommand(fallbackMethod = "defaultUser")
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

    private User defaultUser(String id) {
        return new User("def", "def");
    }

重要的是要记住,Hystrix命令和回退方法应该放在同一个类中并具有相同的方法签名(执行失败的异常(诱发服务降级的异常)为可选参数)。回退方法可以有任何访问修饰符。

方法defaultUser将用于在任何错误的情况下处理回退逻辑。如果您需要将fallback methoddefaultUser作为单独的Hystrix命令运行,那么您需要使用HystrixCommand注释对其进行注释,如下所示:

    @HystrixCommand(fallbackMethod = "defaultUser")
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

    @HystrixCommand
    private User defaultUser(String id) {
        return new User();
    }

如果回退方法标记为@HystrixCommand,那么这种回退方法(defaultUser)也可以有自己的回退方法,如下例所示:

    @HystrixCommand(fallbackMethod = "defaultUser")
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

    @HystrixCommand(fallbackMethod = "defaultUserSecond")
    private User defaultUser(String id) {
        return new User();
    }

    @HystrixCommand
    private User defaultUserSecond(String id) {
        return new User("def", "def");
    }

Javanica提供了在执行fallback中获取执行异常(导致命令失败抛出的异常)的能力。你可以使用附加参数扩展fallback方法签名,以获取命令抛出的异常。
Javanica通过fallback方法的附加参数来公开执行异常。执行异常是通过调用方法getExecutionException()在vanilla hystrix中得到的。

示例:

    @HystrixCommand(fallbackMethod = "fallback1")
    User getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    @HystrixCommand(fallbackMethod = "fallback2")
    User fallback1(String id, Throwable e) {
        assert "getUserById command failed".equals(e.getMessage());
        throw new RuntimeException("fallback1 failed");
    }

    @HystrixCommand(fallbackMethod = "fallback3")
    User fallback2(String id) {
        throw new RuntimeException("fallback2 failed");
    }

    @HystrixCommand(fallbackMethod = "staticFallback")
    User fallback3(String id, Throwable e) {
        assert "fallback2 failed".equals(e.getMessage());
        throw new RuntimeException("fallback3 failed");
    }

    User staticFallback(String id, Throwable e) {
        assert "fallback3 failed".equals(e.getMessage());
        return new User("def", "def");
    }

    // test
    @Test
    public void test() {
        assertEquals("def", getUserById("1").getName());
    }

如你所见,附加的Throwable参数不是强制性的,可以省略或指定。fallback可以得到父层方法执行失败的异常,因此fallback3会收到fallback2抛出的异常,而不是getUserById命令中的异常。

Async/Sync fallback

回退可以是异步或同步,在某些情况下,它取决于命令执行类型,下面列出了所有可能的使用:

支持

case 1: 同步 command, 同步 fallback

   @HystrixCommand(fallbackMethod = "fallback")
    User getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    @HystrixCommand
    User fallback(String id) {
        return new User("def", "def");
    }

case 2: 异步 command, 同步 fallback

    @HystrixCommand(fallbackMethod = "fallback")
    Future<User> getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    @HystrixCommand
    User fallback(String id) {
        return new User("def", "def");
    }

case 3: 异步 command, 异步 fallback

    @HystrixCommand(fallbackMethod = "fallbackAsync")
    Future<User> getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    @HystrixCommand
    Future<User> fallbackAsync(String id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return new User("def", "def");
            }
        };
    }
不支持(禁止)

case 1: 同步 command, 异步 fallback。这种情况是不支持的,因为在本质上,一个调用者执行getUserById方法不会得到Future的结果,并且fallback方法提供的Future结果对调用者根本不可用。
因此执行命令会在调用者获得结果之前强制完成fallbackAsync,已经说过,事实证明,async fallback执行没有任何好处。但是,如果同步和异步命令同时使用某个fallback,这可能是很方便,
如果你看到这种情况是非常有帮助的,那么请来创建问题以便我们很好支持。

    @HystrixCommand(fallbackMethod = "fallbackAsync")
    User getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    @HystrixCommand
    Future<User> fallbackAsync(String id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return new User("def", "def"); // 实际期望获取这个结果,但是被强制执行返回了Future<User>结果,该结果对调用者不可用
            }
        };
    }

case 2: 同步 command, 异步 fallback, 与案例1相同的原因,不支持这种情况。

    @HystrixCommand(fallbackMethod = "fallbackAsync")
    User getUserById(String id) {
        throw new RuntimeException("getUserById command failed");
    }

    Future<User> fallbackAsync(String id) {
        return new AsyncResult<User>() {
            @Override
            public User invoke() {
                return new User("def", "def");
            }
        };
    }

在javanica中使用observable功能有相同的限制。

类或具体命令的默认回退

此功能允许为整个类或具体命令定义默认回退。如果您有一批具有完全相同的回退逻辑的命令,您仍然必须为每个命令定义回退方法,因为回退方法应该具有与命令完全相同的签名,请考虑以下代码:

    public class Service {
        @RequestMapping(value = "/test1")
        @HystrixCommand(fallbackMethod = "fallback")
        public APIResponse test1(String param1) {
            // some codes here
            return APIResponse.success("success");
        }

        @RequestMapping(value = "/test2")
        @HystrixCommand(fallbackMethod = "fallback")
        public APIResponse test2() {
            // some codes here
            return APIResponse.success("success");
        }

        @RequestMapping(value = "/test3")
        @HystrixCommand(fallbackMethod = "fallback")
        public APIResponse test3(ObjectRequest obj) {
            // some codes here
            return APIResponse.success("success");
        }

        private APIResponse fallback(String param1) {
            return APIResponse.failed("Server is busy");
        }

        private APIResponse fallback() {
            return APIResponse.failed("Server is busy");
        }

        private APIResponse fallback(ObjectRequest obj) {
            return APIResponse.failed("Server is busy");
        }
    }

默认回退功能允许采用DRY原则(Don’t Repeat Yourself,不要重复你自己)并摆脱冗余:

    @DefaultProperties(defaultFallback = "fallback")
    public class Service {
        @RequestMapping(value = "/test1")
        @HystrixCommand
        public APIResponse test1(String param1) {
            // some codes here
            return APIResponse.success("success");
        }

        @RequestMapping(value = "/test2")
        @HystrixCommand
        public APIResponse test2() {
            // some codes here
            return APIResponse.success("success");
        }

        @RequestMapping(value = "/test3")
        @HystrixCommand
        public APIResponse test3(ObjectRequest obj) {
            // some codes here
            return APIResponse.success("success");
        }

        private APIResponse fallback() {
            return APIResponse.failed("Server is busy");
        }
    }

默认的回退方法不应该有任何参数,除了可以附加获取执行异常参数,不应该抛出任何异常。以降序优先级列(如果设置1,则不执行2)出如下:

  1. 使用@HystrixCommand的fallbackMethod属性定义命令回退
  2. 使用@HystrixCommand的defaultFallback属性定义命令默认回退
  3. 使用@DefaultProperties的defaultFallback属性定义类默认回退

错误传播

基于此描述,@HystrixCommand具有指定应被忽略的异常类型的能力。

    @HystrixCommand(ignoreExceptions = {BadRequestException.class})
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

如果userResource.getUserById(id);抛出类型为BadRequestException的异常,则此异常将被包装在HystrixBadRequestException中,并重新抛出,而不触发后备逻辑。
你不需要手动执行,javanica会为你做这个。

值得注意的是,默认情况下,一个调用者总是会得到根本原因异常,例如BadRequestException,而不是HystrixBadRequestExceptionHystrixRuntimeException(除非有执行代码显式抛出这些异常的情况)。

可选地,通过使用raiseHystrixExceptions,可以禁用HystrixRuntimeException的拆箱。即所有未被忽略的异常会作为HystrixRuntimeException的cause出现。

    @HystrixCommand(
        ignoreExceptions = {BadRequestException.class},
        raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION})
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

注意:如果命令有一个回退,则只有触发回退逻辑的第一个异常将被传播给调用者。例:

    class Service {
        @HystrixCommand(fallbackMethod = "fallback")
        Object command(Object o) throws CommandException {
            throw new CommandException();
        }

        @HystrixCommand
        Object fallback(Object o) throws FallbackException {
            throw new FallbackException();
        }
    }

    // in client code
    {
        try {
            service.command(null);
        } catch (Exception e) {
        assert CommandException.class.equals(e.getClass())
        }
    }

请求缓存

……

配置

命令属性

命令属性可以使用@HystrixCommand的’commandProperties’设置,如下所示:

    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
        })
    public User getUserById(String id) {
        return userResource.getUserById(id);
    }

Javanica使用Hystrix ConfigurationManager动态设置属性。对于上面的例子,Javanica幕后执行的动作:

ConfigurationManager.getConfigInstance().setProperty("hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500");

更多关于Hystrix命令属性commandfallback

ThreadPoolProperties可以使用@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) {
        return userResource.getUserById(id);
    }

默认属性

@DefaultProperties是类(类型)级别注释,允许设置的默认命令属性,有groupKeythreadPoolKeycommandPropertiesthreadPoolProperties
ignoreExceptionsraiseHystrixExceptions
默认情况下,使用此注释指定的属性将在注释类中定义的每个hystrix命令使用,除非命令明确使用相应的@HystrixCommand参数来指定这些属性(覆盖默认行为)。例:

    @DefaultProperties(groupKey = "DefaultGroupKey")
    class Service {
        @HystrixCommand // hystrix command group key is 'DefaultGroupKey'
        public Object commandInheritsDefaultProperties() {
            return null;
        }
        @HystrixCommand(groupKey = "SpecificGroupKey") // command overrides default group key
        public Object commandOverridesGroupKey() {
            return null;
        }
    }

Hystrix collapser

……

原文链接: hystrix-javanica & 推荐阅读

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值