Hystrix 详解

Hystrix入门指南

Introduction

 

1、Where does the name come from?

hystrix对应的中文名字是“豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用了对应的卡通形象做作为logo。

2、What Is Hystrix?

在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。

 

   

3、Hello Hystrix

 

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();
Observable<String> s = new CommandHelloWorld("Bob").toObservable()

 

    说明:

    • execute() — blocks, then returns the single response received from the dependency (or throws an exception in case of an error)
    • queue() — returns a Future with which you can obtain the single response from the dependency
    • observe() — subscribes to the Observable that represents the response(s) from the dependency and returns an Observable that replicates that source Observable
    • toObservable() — returns an Observable that, when you subscribe to it, will execute the Hystrix command and emit its responses

 

4、Flow Chart

 

 说明:

  1. Construct a HystrixCommand or HystrixObservableCommand Object
  2. Execute the Command
  3. Is the Response Cached?
  4. Is the Circuit Open?
  5. Is the Thread Pool/Queue/Semaphore Full?
  6. HystrixObservableCommand.construct() or HystrixCommand.run()
  7. Calculate Circuit Health
  8. Get the Fallback
  9. Return the Successful Response

 常用功能介绍

依赖隔离

一个用户请求的成功执行,肯能依赖数十上百个外部服务,如果没有隔离,单个依赖的失败,可能会印象其他依赖的正常执行。如下图所示,为每个依赖配置了单独线程池

   

在下图中,当Dep I 出现问题时,DepA 和Dep M大以来可以正常执行

 

 线程池隔离的使用例子

复制代码

 1 public class CommandHelloWorld extends HystrixCommand<String> {
 2  
 3     private final String name;
 4  
 5     public CommandHelloWorld(String name) {
 6         super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  //必须
 7                 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))  //可选,默认 使用 this.getClass().getSimpleName();
 8                 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4)));
 9          
10         this.name = name;
11     }
12  
13     @Override
14     protected String run() throws InterruptedException {
15         System.out.println("running");
16         TimeUnit.MILLISECONDS.sleep(1000);
17         return "Hello " + name + "!";
18     }
19      
20 }

复制代码

线程池常用参数设置:

实现类:HystrixThreadPoolProperties

名称

类型

含义

默认值

coreSize
Integer
线程池大小10
maxQueueSize
Integer
队列大小,一经初始化后不能修改-1
queueSizeRejectionThreshold
Integer
队列reject阈值,可以动态修改
maxQueueSize>0是生效,一般设置为小于
maxQueueSizede 的数值
 
5

 

 

 

 

 

 

 

 

 

 

 
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  //必须
 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                .withExecutionTimeoutInMilliseconds(500))  //超时时间
 .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))  //可选,默认 使用 this.getClass().getSimpleName();
 .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4)
        .withMaxQueueSize(10).withQueueSizeRejectionThreshold(7))

 

 

Q: 怎么设置线程池大小?

  A:Qps* Tp99 +冗余线程

信号量隔离

线程池隔离中,发起请求的线程和真实执行的线程不是同一个线程,使用信号量隔离时,它们是同一个线程, 两种隔离的区别如下图:

public class CommandUsingSemaphoreIsolation extends HystrixCommand<String> {
 
    private final int id;
    private long start,end ;
 
    public CommandUsingSemaphoreIsolation(int id) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                // since we're doing an in-memory cache lookup we choose SEMAPHORE isolation
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE) //设置使用信号量隔离策略
                                .withExecutionIsolationSemaphoreMaxConcurrentRequests(3)  //设置信号量隔离时的最大并发请求数
                                .withFallbackIsolationSemaphoreMaxConcurrentRequests(5)     //设置fallback的最大并发数
                        .withExecutionTimeoutInMilliseconds(300)));   //设置超时时间
        this.id = id;
        this.start = System.currentTimeMillis();
    }
 
    @Override
    protected String run() throws InterruptedException {
        // a real implementation would retrieve data from in memory data structure
        TimeUnit.MILLISECONDS.sleep(id*30);
        System.out.println("running normal, id="+id);
        return "ValueFromHashMap_" + id;
    }
 
    @Override
    protected String getFallback(){
        System.out.println(" fallback, id="+id);
        return "fallback:"+id;
    }
 
}
  
@Test
public void maxCurrentRequst() throws InterruptedException {
    int count =10;
    while (count >0){
        int id = count--;
        new Thread(() -> {
            try {
                new CommandUsingSemaphoreIsolation(id).execute();
            }catch (Exception ex){
                System.out.println("Exception:"+ex.getMessage()+" id="+id);
            }
        }).start();
    }
 
    TimeUnit.SECONDS.sleep(100);
}
 //注:使用信号量隔离,在同一个线程中即使循环调用new CommandUsingSemaphoreIsolation(id).queue(),run方法也是顺序执行;

 

//控制台输出

fallback, id=10
fallback, id=9
fallback, id=5
fallback, id=8
fallback, id=1
Exception:CommandUsingSemaphoreIsolation fallback execution rejected. id=4
Exception:CommandUsingSemaphoreIsolation fallback execution rejected. id=7
running normal, id=2
running normal, id=3
running normal, id=6

Q: 什么时候使用线程池隔离,什么使用信号量隔离?

A:  线程池隔离缺点是带来一定的开销,但不会阻塞请求线程,适合于于IO密集型的任务

信号量隔离使用用户请求线程,没有格外线程切换开销,使用与执行时间和执行逻辑都比较短的本地计算。比如CPU密集型的任务

Fallback

Q1: 为什么需要fallback?

简单来说,在依赖调用失败时,我们一般会需要提供降级方案,Hystrix对此提供了支持

降级 

public class CommandHelloWorld extends HystrixCommand<String> {
 
    private final String name;
 
    public CommandHelloWorld(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  //必须
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionTimeoutInMilliseconds(500))  //超时时间
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))  //可选,默认 使用 this.getClass().getSimpleName();
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4)));
 
        this.name = name;
    }
 
    @Override
    protected String run() throws InterruptedException {
        System.out.println("running");
        TimeUnit.MILLISECONDS.sleep(1000);
        return "Hello " + name + "!";
    }
 
    @Override
    protected String getFallback() {
        return "Hello "+"Fallback";
    }
}
  
@Test
public void fallbackTest(){
    assertEquals("Hello Fallback",new CommandHelloWorld("World").execute());
}

 

 

 Q2:什么情况下会触发fallback?

简单来说,就是run方法抛异常,超时,线程/信号量reject、短路

Failure Type

Exception class

Exception.cause

subject to fallback

FAILUREHystrixRuntimeExceptionunderlying exception (user-controlled)YES
TIMEOUTHystrixRuntimeExceptionj.u.c.TimeoutExceptionYES
SHORT_CIRCUITEDHystrixRuntimeExceptionj.l.RuntimeExceptionYES
THREAD_POOL_REJECTEDHystrixRuntimeExceptionj.u.c.RejectedExecutionExceptionYES
SEMAPHORE_REJECTEDHystrixRuntimeExceptionj.l.RuntimeExceptionYES
BAD_REQUESTHystrixBadRequestExceptionunderlying exception (user-controlled)NO

 

 

 

 

 

 

 

 

以下为测试的主程序:

public class CommandHelloFailure extends HystrixCommand<String> {
 
    private final String name;
 
    public CommandHelloFailure(String name) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  //必须
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionTimeoutInMilliseconds(1000))  //超时时间
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(3)));
 
        this.name = name;
    }
 
    @Override
    protected String run() throws InterruptedException {
        String theadName = this.getThreadPoolKey().name();
        String cmdKey=this.getThreadPoolKey().name();
        System.out.println("running begin , threadPool="+theadName+" cmdKey="+cmdKey+" name="+name);
 
        if("Exception".equals(name)) {
            throw new RuntimeException("this command always fails");
        }else if("Timeout".equals(name)){
            TimeUnit.SECONDS.sleep(2);
        }else if("Reject".equals(name)){
            TimeUnit.MILLISECONDS.sleep(800);
        }
        System.out.println(" run end");
 
        return "Hello " + name + "!";
    }
 
    @Override
    protected String getFallback() {
        StringBuilder sb = new StringBuilder("running fallback");
        boolean isRejected = isResponseRejected();
        boolean isException = isFailedExecution();
        boolean isTimeout= isResponseTimedOut();
        boolean isCircut = isCircuitBreakerOpen();
 
        sb.append(", isRejected:").append(isRejected);
        sb.append(", isException:"+isException);
        if(isException){
            sb.append(" msg=").append(getExecutionException().getMessage());
        }
        sb.append(",  isTimeout: "+isTimeout);
        sb.append(",  isCircut:"+isCircut);
 
        sb.append(", group:").append(this.getCommandGroup().name());
        sb.append(", threadpool:").append(getThreadPoolKey().name());
        System.out.println(sb.toString());
 
        String msg="Hello Failure " + name + "!";
        return msg;
    }
}

 

 

FAILURE 

测试由异常导致的fallback

1 @Test
2 public void expTest() {
3     assertEquals("Hello Failure Exception!", new CommandHelloFailure("Exception").execute());
4 }
5   
//控制台输出

running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Exception
running fallback, isRejected:false, isException:true msg=this command always fails, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool

 

TIMEOUT

测试有超时导致的fallback

 

@Test
public void timeOutTest() {
    assertEquals("Hello Failure Timeout!", new CommandHelloFailure("Timeout").execute());
}
  
//控制台输出

running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Timeout
running fallback, isRejected:false, isException:false, isTimeout: true, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool

 

THREAD_POOL_REJECTED

并发执行的任务数超过线程池和队列之和会被reject,导致fallback

@Test
public void rejectTest() throws InterruptedException {
    int count = 5;
    while (count-- > 0){
        new CommandHelloFailure("Reject").queue();
        TimeUnit.MILLISECONDS.sleep(100);
    }
}
 

//控制台输出

running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running begin , threadPool=ExampleGroup-pool cmdKey=ExampleGroup-pool name=Reject
running fallback, isRejected:true, isException:false, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool
running fallback, isRejected:true, isException:false, isTimeout: false, isCircut:false, group:ExampleGroup, threadpool:ExampleGroup-pool

SEMAPHORE_REJECTED  与 THREAD_POOL_REJECTED 类似,不再演示

 

SHORT_CIRCUITED

在一定时间内,用户请求超过一定的比例失败时(timeout, failure, reject),断路器就会打开;短路器打开后所有请求直接走fallback

参数设置

名称

类型

含义

默认值

circuitBreakerEnabledBoolean是否启用断路器true
circuitBreakerErrorThresholdPercentageInteger错误百分比,超过该值打开断路器50
circuitBreakerForceClosedBoolean强制断路器打开false
circuitBreakerForceOpenBoolean强制短路器关闭false
circuitBreakerRequestVolumeThresholdInteger10s中内最少的请求量,大于该值,断路器配置才会生效20
circuitBreakerSleepWindowInMillisecondsInteger短路器打开后多长时间尝试关闭(Half open)5s

 

 

 

 

 

 

 

 

一般配置如下:

Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))  //必须
        .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                .withExecutionTimeoutInMilliseconds(50)//超时时间
                .withCircuitBreakerRequestVolumeThreshold(5)
                .withCircuitBreakerSleepWindowInMilliseconds(1000)
                .withCircuitBreakerErrorThresholdPercentage(50))
        .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("ExampleGroup-pool"))  //可选,默认 使用 this.getClass().getSimpleName();
        .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(4));

 

以上配置的含义是: 在10s内,如果请求在5个及以上,且有50%失败的情况下,开启断路器;断路器开启1000ms后尝试关闭

短路器的工作机制,引用自官方文档:

The precise way that the circuit opening and closing occurs is as follows:
Assuming the volume across a circuit meets a certain threshold (HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())...
And assuming that the error percentage exceeds the threshold error percentage (HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())...
Then the circuit-breaker transitions from CLOSED to OPEN.
While it is open, it short-circuits all requests made against that circuit-breaker.
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.

Q3:fallback时我们应该怎么办?

一般有以下几种策略:

1、不实现getFallback方法:依赖调用失败时直接抛出异常

2、实现getFallback方法,返回默认值:这是一种常见的策略

3、实现getFallback方法,走降级方案

此外,生产环境中,fallback时,一般需要打点记录

请求合并

简单来说,就是将一段时间内的多次请求合并为一次请求,常用于网络IO中,能减少IO次数,缺点是增加平均延迟

 

以下是测试代码主程序:

public class CommandCollapserGetValueForKey extends HystrixCollapser<List<String>, String, Integer> {
 
    private final Integer key;
 
    public CommandCollapserGetValueForKey(Integer key) {
        super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("Collapser"))
                .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
                        .withMaxRequestsInBatch(3)
                .withTimerDelayInMilliseconds(10)));
        this.key = key;
    }
 
    @Override
    public Integer getRequestArgument() {
        return key;
    }
 
    @Override
    protected HystrixCommand<List<String>> createCommand(final Collection<CollapsedRequest<String, Integer>> requests) {
        return new BatchCommand(requests);
    }
 
    @Override
    protected void mapResponseToRequests(List<String> batchResponse, Collection<CollapsedRequest<String, Integer>> requests) {
        int count = 0;
        for (CollapsedRequest<String, Integer> request : requests) {
            request.setResponse(batchResponse.get(count++));
        }
    }
 
    private static final class BatchCommand extends HystrixCommand<List<String>> {
        private final Collection<CollapsedRequest<String, Integer>> requests;
 
        private BatchCommand(Collection<CollapsedRequest<String, Integer>> requests) {
            super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
                    .andCommandKey(HystrixCommandKey.Factory.asKey("GetValueForKey")));
            this.requests = requests;
        }
 
        @Override
        protected List<String> run() {
            System.out.println("BatchCommand run  "+requests.size());
            ArrayList<String> response = new ArrayList<String>();
            for (CollapsedRequest<String, Integer> request : requests) {
                // artificial response for each argument received in the batch
                response.add("ValueForKey: " + request.getArgument());
            }
            return response;
        }
    }
}
  
  
@Test
public void testCollapser() throws Exception {
    HystrixRequestContext context = HystrixRequestContext.initializeContext();
    try {
        Future<String> f1 = new CommandCollapserGetValueForKey(1).queue();
        Future<String> f2 = new CommandCollapserGetValueForKey(2).queue();
        Future<String> f3 = new CommandCollapserGetValueForKey(3).queue();
        Future<String> f4 = new CommandCollapserGetValueForKey(4).queue();
 
 
        assertEquals("ValueForKey: 1", f1.get());
        assertEquals("ValueForKey: 2", f2.get());
        assertEquals("ValueForKey: 3", f3.get());
        assertEquals("ValueForKey: 4", f4.get());
 
        // assert that the batch command 'GetValueForKey' was in fact
        // executed and that it executed only once
        assertEquals(2, HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().size());
        HystrixCommand<?> command = HystrixRequestLog.getCurrentRequest().getAllExecutedCommands().toArray(new HystrixCommand<?>[1])[0];
        // assert the command is the one we're expecting
        assertEquals("GetValueForKey", command.getCommandKey().name());
        // confirm that it was a COLLAPSED command execution
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.COLLAPSED));
        // and that it was successful
        assertTrue(command.getExecutionEvents().contains(HystrixEventType.SUCCESS));
    } finally {
        context.shutdown();
    }
}
  
//控制输出
BatchCommand run  3
BatchCommand run  1

 

 

使用该特性

1、必须继承HystrixCollapser类,

2、实现以下方法:

getRequestArgument: 返回请求参数对象

createCommand : 返回BatchCommand

mapResponseToRequests:实现Response和Request的映射

3、创建对应的BatchCommand类:批量请求的具体实现

 

参数配置:

名称

类型

含义

默认值

maxRequestsInBatch
Integer
每个批次最大的请求数,超过该值,创建新的batch请求
Integer.MAX_VALUE
timerDelayInMilliseconds
Integer
等待时间窗口,超过该值,创建新的batch请求10ms
requestCacheEnabled
Boolean
是否启用cachetrue

 

 

 

 

 

 

一般配置如下

Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("Collapser"))
       .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter()
               .withMaxRequestsInBatch(3)
       .withTimerDelayInMilliseconds(5));

 

请求cache

 

public class CommandUsingRequestCache extends HystrixCommand<Boolean> {
    private final int value;
     
    public CommandUsingRequestCache(int value) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.value = value;
    }
 
    @Override
    public Boolean run() {
        return value == 0 || value % 2 == 0;
    }
 
   //使用cache功能,必须实现该方法
    @Override
    public String getCacheKey() {
        return String.valueOf(value);
    }
}
  
@Test
public void testWithCacheHits() {
    HystrixRequestContext context = HystrixRequestContext.initializeContext();
    try {
        CommandUsingRequestCache command2a = new CommandUsingRequestCache(2);
        CommandUsingRequestCache command2b = new CommandUsingRequestCache(2);
 
        assertTrue(command2a.execute());
        //第一次请求,没有cache
        assertFalse(command2a.isResponseFromCache());
 
        assertTrue(command2b.execute());
        // 第二次请求,从cache中拿的结果
        assertTrue(command2b.isResponseFromCache());
    } finally {
        context.shutdown();
    }
 
    context = HystrixRequestContext.initializeContext();
    try {
        CommandUsingRequestCache command3b = new CommandUsingRequestCache(2);
        assertTrue(command3b.execute());
        // this is a new request context so this
        //new了新的 request context后,之前的cache失效
        assertFalse(command3b.isResponseFromCache());
    } finally {
        context.shutdown();
    }
}

Hystrix Context

 Global Context

UserRequest Context

 

使用与监控

1、工程中使用

使用Hystrix很简单,只需要添加相应依赖即可,以Maven为例:

<!-- hystrix 依赖 -->
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.9</version>
</dependency>
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-metrics-event-stream</artifactId>
    <version>1.5.9</version>
</dependency>

 

2、DashBoard使用 

web.xml中配置相应的Servlet

<servlet>
          <display-name>HystrixMetricsStreamServlet</display-name>
          <servlet-name>HystrixMetricsStreamServlet</servlet-name>
          <servlet-class>com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet</servlet-class>
</servlet>
<servlet-mapping>
          <servlet-name>HystrixMetricsStreamServlet</servlet-name>
          <url-pattern>/hystrix.stream</url-pattern>
   </servlet-mapping>

 

 

下载附件中的war文件和jar文件到任意目录,执行

java -jar jetty-runner-9.2.10.v20150310.jar --port 8410 hystrix-dashboard-1.5.1.war

然后在浏览器中打开:http://localhost:8410/  ,在输入框中填写 http://hostname:port/application/hystrix.stream, 点击 Add Stream ,然后在点击Monitor Stream。

 

一般来说: Thread-pool Rejections  和Failuress/Exception应该是0,Thread timeouts是个很小的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值