【Java】Hystrix

命令使用

我们需要构建封装调用的类,然后new该类的实例来执行hystrix调用。类的构建需要继承HystrixCommand 或者 HystrixObservableCommand。通常,继承自HystrixCommand足以。HystrixObservableCommand使用的是rxJava的模式,本质一样,语法不同而已。
下面以HystrixObservableCommand为例。由于该类没有无参构造方法,所以我们需要在子类构造方法中显示调用HystrixCommand构造方法来指定一些属性。另外还要实现run方法来写具体的调用过程。

定义好类以后,就是new实例执行调用了。分三种情况。
1)同步调用,有两种形式:
2)非响应式异步调用,也就是future的get不直接调用,先queue在get:
3)响应式异步调用,响应式其实就是回调,调用结束后我们不需要主动获取结果,而是在调用时就传入一个回调函数,调用结束后触发回调处理结果。借助的是rxJava的观察者模式。hystrix重写了部分方法,大体就是command执行结束,调用observer的onNext后者onError处理结果。需要注意的是,command执行是在另外的线程池中,所以主线程要sleep才能看到回调:

public class HelloCommand extends HystrixCommand<String> {

    String param;

    protected HelloCommand(String param) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(3000)));
        this.param = param;
    }

    @Override
    protected String run() throws Exception {
        Thread.sleep(2000);
        return "ly " + param ;
    }

    @Override
    protected String getFallback(){
        return "wrong";
    }


    public static void main(String args[]) throws ExecutionException, InterruptedException {
        // 同步式
        System.out.println(new HelloCommand("jaja").execute());
        System.out.println(new HelloCommand("jaja").queue().get());

        // 异步式
        Future<String> s = new HelloCommand("jaja").queue();
        System.out.println("rrr");
        System.out.println(s.get());

        // 响应式
        new HelloCommand("jaja").observe().subscribe(new Observer<String>() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable throwable) {

            }

            @Override
            public void onNext(String s) {
                System.out.println(s);
            }
        });
        // 保证回调可以看到
        Thread.sleep(10000);
    }

FailBack
要注意的是,如果failback的值也要network call,就需要再包一层hystrix command。

public class FailCommand extends HystrixCommand<String> {

    String param;

    protected FailCommand(String param) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(3000)));
        this.param = param;
    }

    @Override
    protected String run() throws Exception {
        //Thread.sleep(20000);
        throw new NullPointerException();
        //return "ly " + param;
    }

    @Override
    protected String getFallback() {
        return "wrong";
    }

    public static void main(String args[]){
        System.out.println(new FailCommand("jaja").execute());
    }

}

CommandName
默认是类名:getClass().getSimpleName(),当然可以在super中通过Setter指定,之前的代码里。CommandName其实没有太大作用,除了在后面动态配置中根据这个name配置属性。

CommandGroup
命令组名,逻辑分组,使用HystrixCommandGroupKey。指南上写着用于出报表,暂时没用过。

Thread-pool key
线程key,使用HystrixThreadPoolKey,这个比较重要,不同的thread key对应不同的线程,最终监控,短路等都是以这个为单位的。如果不指定默认使用的是CommandGroupKey。

Cache
hystrix提供了缓存功能,需要实现getCacheKey方法,如果key一样,就返回缓存的值。另外使用缓存必须让command运行在同一个context下,需要开启context,否则报错。

public class CacheCommand extends HystrixCommand<Integer> {

    Integer param;

    protected CacheCommand(Integer param) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(3000)));
        this.param = param;
    }

    @Override
    protected Integer run() throws Exception {
        return new Random().nextInt(100);
    }

    @Override
    protected Integer getFallback() {
        return -1;
    }

    @Override
    protected String getCacheKey() {
        return "" + param % 2;
    }

    public static void main(String args[]){
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        System.out.println(new CacheCommand(100).execute());
        System.out.println(new CacheCommand(102).execute());
    }

断路器
原理是维持一个时间窗口,统计时间窗口内的超时,错误的比例,达到阈值就打开断路器,随后的请求全部都failback,然后再过一段时间变成半开半闭,即放一个请求进来,如果没有问题就关闭否则仍旧打开。
hystrix也不是每一个请求都计算统计信息,而是分桶,桶的数目可以根据配置指定。随着时间的流逝,新的请求所在的桶成型了再一起计算新桶内的统计值,这样可以降级cpu的消耗。同时丢弃最旧的桶。
关于断路器的逻辑更详细的在官网:https://github.com/Netflix/Hystrix/wiki/How-it-Works

  1. Assuming the volume across a circuit meets a certain threshold (HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...
  2. And assuming that the error percentage exceeds the threshold error percentage (HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...
  3. Then the circuit-breaker transitions from CLOSED to OPEN.
  4. While it is open, it short-circuits all requests made against that circuit-breaker.
  5. After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next single request is let through (this is the HALF-OPEN state). If the request fails, the circuit-breaker returns to the OPEN state for the duration of the sleep window. If the request succeeds, the circuit-breaker transitions to CLOSED and the logic in 1. takes over again.

属性
hystrix有很多,下面分类介绍下主要的属性。
hystrix配置属性有4个level,优先级从低到高依次为:
hystrix代码写死的默认值;
从全局配置文件读到的默认值;
实例静态默认值,这个指的就是构建HystrixCommand时,通过注解或者给父类构造器传参的方式设置默认值,具体配置项的名字在官网有权威总结。
实例动态值,这个需要专门介绍下,感觉官网的解释很少。但是这个功能确是相当有用啊,因为我们非常有可能在程序运行时动态修改配置,比如超时时间或者线程池大小等等。需要借助ConfigurationManager类来重设置,下面有例子。另外配置项的key要稍作修改,把上面的实例静态默认值中的“default”修改为{hystrixCommandKey},也就是命令的名字。
比如:静态配置名为“hystrix.command.HystrixCommandKey.execution.isolation.thread.timeoutInMilliseconds”,我们的commnd名字为“HelloCommand”,那么动态的配置名就是“hystrix.command.HelloCommand.execution.isolation.thread.timeoutInMilliseconds”
 

public class ConfigCommand extends HystrixCommand<String> {

    String param;

    protected ConfigCommand(String param) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("TestCommand"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(3000)));
        this.param = param;
    }

    @Override
    protected String run() throws Exception {
        Thread.sleep(2000);
        return "ly " + param;
    }

    @Override
    protected String getFallback() {
        return "wrong";
    }

    public static void main(String args[]){
        System.out.println(new ConfigCommand("jaja").execute());
        //hystrix.command.TestCommand.execution.isolation.thread.timeoutInMilliseconds
        ConfigurationManager.getConfigInstance().setProperty("hystrix.command.TestCommand.execution.isolation.thread.timeoutInMilliseconds", 1000);
        System.out.println(new ConfigCommand("jaja").execute());
    }

}

第一个调用没有超时,第二个超时了。

ly jaja
2019-02-28 15:59:44 DEBUG HystrixPropertiesChainedProperty:236 - Property changed: 'hystrix.command.TestCommand.execution.isolation.thread.timeoutInMilliseconds = 1000'
2019-02-28 15:59:44 DEBUG HystrixPropertiesChainedProperty:90 - Flipping property: hystrix.command.TestCommand.execution.isolation.thread.timeoutInMilliseconds to use its current value: 1000
wrong

可以从日志看到超时属性变了。

下面分组过一下关键的配置项,这里只是介绍含义,具体的名字和默认值在官网:https://github.com/Netflix/Hystrix/wiki/Configuration


Execution
execution.isolation.strategy:隔离策略,有两种:
Thread:线程隔离,推荐的方式,如果下游服务挂了,调用方可以被安全隔离。但是中间加了一个线程池,对服务器的资源有一定消耗。官网说,只要qps不是太大,消耗的线程资源可以接受,就用这种方式。
Semaphore:信号量,中间没有线程池,调用方直接在当前线程下调用下游服务,通过限制并发请求的数目来隔离,一旦下游挂了,调用方资源不能及时释放,比如web服务线程将被一直消耗着。
execution.isolation.thread.timeoutInMilliseconds:服务超时时间,单位毫秒,默认1000.
execution.timeout.enabled:是否打开超时检测。
execution.isolation.semaphore.maxConcurrentRequests:使用信号量隔离时qps阈值,后续的请求会被拒。

Circuit Breaker
circuitBreaker.enabled:是否打开断路器
circuitBreaker.requestVolumeThreshold:断路器检测的基础请求值,只有时间窗口内的请求数达到这个阈值时,才会判定错误率,否则比如只有一两个请求,即便都失败了,也不会打开断路器,因为基数太少了。
circuitBreaker.sleepWindowInMilliseconds:指的是从断路器打开状态到半开半闭状态需要的时间,即断路后,需要等多久才能放一个请求进来。
circuitBreaker.errorThresholdPercentage:错误百分比,超过就会短路。

Metrics
metrics.rollingStats.timeInMilliseconds:时间窗口大小
metrics.rollingStats.numBuckets:时间窗口的桶的数目,必须能被时间窗口大小整除,否则报错。
metrics.healthSnapshot.intervalInMilliseconds:每一次检测的间隙。因为就算分窗口统计错误率,也会很占cpu,所以每一次统计都会等一个时间间隔再开始。
metrics.rollingPercentile.enabled:带rollingPercentile的都表示调用时延的统计,该选项表示是否打开时延统计,比如说95分位99分位等,如果关闭都返回-1.
metrics.rollingPercentile.timeInMilliseconds:时延统计的时间窗口。
metrics.rollingPercentile.numBuckets:时延统计的桶数目。
metrics.rollingPercentile.bucketSize:时延统计的桶大小,时延统计每一个桶只维持最新的该数值的请求的数据,早一些的将会被覆盖。

Thread pool
coreSize:核心线程数,不会变
maximumSize:最大线程数,如果不够了就进入等待队列。该属性是最大可并发的请求数。
maxQueueSize:等待队列,还超就会被拒。这个数值无法动态修改。
queueSizeRejectionThreshold:进入queue时被拒的概率值,即便是没有达到maxQueueSize。这个为了弥补上面无法动态修改的不足。可以通过这个概率值来控制队列大小。
这些参数好像和线程池的参数含义略有不同。


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值