SpringCloud - Circuit Breaker Hystrix

Introduction

Hystrix is ​​an open source project of Netflix. It can prevent cascading failures of services and cause service avalanches by isolating system-dependent services in the event of service failure. At the same time, Hystrix also provides a failure rollback mechanism, which enables the system to recover from exceptions faster. Hystrix provides protection and control for inter-service calls.

Hystrix has the following functions:

  • It can provide a protection mechanism for the system when there is a high delay or the call fails to call the service through the client
  • In complex distributed scenarios, the service avalanche effect can be prevented
  • Provides fail fast (Fail Fast) and fast recovery
  • Provide failure rollback and graceful service degradation mechanism
  • Provides near real-time monitoring, alarming and operation and maintenance control

Hystrix can be used in a variety of ways in the actual application process, either through annotations or by integrating HystrixCommand and HystrixObservableCommand. This article will briefly illustrate how to use it through a case.

Environmental preparation

category value
JDK 1.8.0_162
SOFABoot / SpringBoot 3.0.0/2.0.x.RELEASE
SpringCloud Finchley.RC1
HERE IDEA

Engineering background

This section will create a sofa-hystrix-client project to implement the fuse and downgrade of the service through the load balancer hystrix provided by Spring Cloud.

Create a new sofa-hystrix-client

This project continues to use the parent project in "SpringCloud-Eureka Service Registration" to build.

Right-click on the parent project of sofa-eureka-parent -> New -> Module, select the Maven project here;

  • artifactId:sofa-hystrix-client

Modify the pom file

Add hysterix dependencies to the pom file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<dependencies>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>    
        
        
    

    <dependency>
<groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-ribbon</artifactId></dependency>        
        
    

    <dependency>
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>        
        
    

    <dependency>
<groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency></dependencies>        
        
    
 

configuration file

1
2
3
4
5
6
7
8
9
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: hystrix-client
server:
  port: 8787

There is no special configuration, and it still exists as an eureka-client.

startup class

Add the annotation @EnableCircuitBreaker to enable the circuit breaker on the startup class

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
@EnableCircuitBreaker
public class SofaHystrixApplication {

    @Bean
@LoadBalancedpublic RestTemplate restTemplate(){returnnew RestTemplate();    }publicstaticvoidmain(String[] args){        SpringApplication.run(SofaHystrixApplication.class, args);    }}    
    
         

        


resource class

  • NormalService

中通过@HystrixCommand标准一个受保护的资源方法 getByServiceId()。getByServiceId 中通过restTemplate 来调用远程服务。@HystrixCommand注解的 fallbackMethod 属性指定当服务不可用时需要执行的 fallback 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class NormalService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "fallBack")
    public String getByServiceId(){
        return restTemplate.getForObject("http://HELLOSOFABOOTSERVICE/hello",String.class);
    }

    private String fallBack(){
        return "Filed to get data";
    }
}
  • HystrixRibbonController:通过instanceService调用上面的NormalService资源类
1
2
3
4
5
6
7
8
9
@RestController
public class HystrixRibbonController {
    @Autowired
    public NormalService instanceService;
    @RequestMapping("/hystrix")
    public String test(){
        return instanceService.getByServiceId();
    }
}

启动&验证

先后启动sofa-eureka-server-center 、sofa-eureka-provider、sofa-hystrix-client 三个工程。浏览器中输入:

http://localhost:8787/hystrix ,结果如下:

1
Hello SOFA! Now Port is 8086 And hostname is HelloSOFABootService

关闭 sofa-eureka-provider ,刷新浏览器:

1
Filed to get data

执行了 NormalService 中的 fallback 方法了。

资源隔离

hystrix 中提供了两中隔离策略,一种是基于线程池的隔离、另外一种是基于信号量的隔离。本篇只演示案例,具体原理请参看 hystrix 原理分析 相关文章。

基于线程池的隔离实现

新建一个 SofaThreadPoolHystrixCommand 类,继承 HystrixCommand。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class SofaThreadPoolHystrixCommand extends HystrixCommand {

    private RestTemplate restTemplate;

    public SofaThreadPoolHystrixCommand(RestTemplate restTemplate) {
        super(initailize());
        this.restTemplate = restTemplate;
    }

    public static HystrixCommand.Setter initailize(){
        // 线程池配置
        HystrixThreadPoolProperties.Setter hystrixThreadPoolProperties = 
            HystrixThreadPoolProperties.Setter()
                .withCoreSize(5)
                .withKeepAliveTimeMinutes(5)
                // 线程等待队列最大长度,默认值:-1 表示不等待直接拒绝,测试表明线程池使用直接决绝策略+ 合适大小的非回缩线程池效率最高.所以不建议修改此值。
                .withMaxQueueSize(10)
                .withQueueSizeRejectionThreshold(100);

        // 命令属性配置,这里指定隔离策略是 THREAD
        HystrixCommandProperties.Setter hystrixCommand = 
            HystrixCommandProperties.Setter() .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                //意味着线程最多允许执行fallback的并发数为10,超过10 报fallback execution rejected
                .withFallbackIsolationSemaphoreMaxConcurrentRequests(10);

        HystrixCommand.Setter setter = HystrixCommand.Setter
            .withGroupKey(HystrixCommandGroupKey.Factory.asKey("SofaThreadPoolHystrixCommand"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("sofaBootService"))
                .andCommandPropertiesDefaults(hystrixCommand)
                .andThreadPoolPropertiesDefaults(hystrixThreadPoolProperties)
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("sofa-hystrix-thread"));

        return setter;
    }

    /**
     * 受保护的资源
     * @return
     * @throws Exception
     */
    @Override
    protected Object run() throws Exception {
        return restTemplate.getForObject("http://HELLOSOFABOOTSERVICE/hello",String.class);
    }

    /**
     * 失败执行的保护方法
     * @return
     */
    @Override
    protected Object getFallback() {
        return "this is fail back policy";
    }
}

相关参数说明:

  • HystrixCommandGroupKey:配置全局唯一标识服务分组的名称,比如账户系统就是一个服务分组,监控时,相同分组的服务会聚合在一起,必填选项。
  • HystrixCommandKey:配置全局唯一标识服务的名称,比如账户系统有一个获取账号名的服务,那么就可以为这个服务起一个名字来唯一识别该服务,如果不配置,则默认是简单类名。
  • HystrixThreadPoolKey:配置全局唯一标识线程池的名称,相同线程池名称的线程池是同一个,如果不配置,则默认是分组名,此名字也是线程池中线程名字的前缀。
  • HystrixThreadPoolProperties:配置线程池参数
  • HystrixCommandProperties:配置该命令的一些参数,如 executionIsolationStrategy 配置执行隔离策略,默认是使用线程隔离。配置为 THREAD,线程池隔离;配置为 SEMAPHORE ,信号量隔离

这里为了模拟并发,使用 CountDownLatch 类来控制,在 HystrixRibbonController 中添加 testThread 资源方法:

1
2
3
4
5
6
7
8
9
@RequestMapping("/testThread")
public String testThread(){
    CountDownLatch countDownLatch = new CountDownLatch(1);
    for (int i = 0; i < THREAD_NUM; i ++) {
        new Thread(new ConsumerThread(countDownLatch)).start();
    }
    countDownLatch.countDown();
    return "data";
}

内部定义一个内部类,模拟调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ConsumerThread implements Runnable {
        private final CountDownLatch startLatch;
        public ConsumerThread(CountDownLatch startLatch) {
            this.startLatch = startLatch;
        }
        @Override
        public void run() {
            try {
                // 线程等待
                startLatch.await();
                // 执行操作
                SofaThreadPoolHystrixCommand sofaThreadPoolHystrixCommand = new SofaThreadPoolHystrixCommand(restTemplate);
                System.out.println(sofaThreadPoolHystrixCommand.execute().toString());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

重启当前工程,浏览器执行 http://localhost:8787/testThread

1
2
3
4
5
6
7
8
this is fail back policy
this is fail back policy
this is fail back policy
this is fail back policy
this is fail back policy
// ... 省略
Hello SOFA! Now Port is 8086 And hostname is HelloSOFABootService
// ... 省略

基于信号量隔离

新建一个 SofaSemaphoreHystrixCommand 类,继承 HystrixCommand。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class SofaSemaphoreHystrixCommand extends HystrixCommand {
    private RestTemplate restTemplate;
    public SofaSemaphoreHystrixCommand(RestTemplate restTemplate) {
        super(initailize());
        this.restTemplate = restTemplate;
    }
    public static HystrixCommand.Setter initailize(){
        // 命令属性配置,这里指定隔离策略是 THREAD
        HystrixCommandProperties.Setter hystrixCommand = HystrixCommandProperties.Setter()
                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                 //至少有10个请求,熔断器才进行错误率的计算
                .withCircuitBreakerRequestVolumeThreshold(0)
                //熔断器中断请求5秒后会进入半打开状态,放部分流量过去重试
                .withCircuitBreakerSleepWindowInMilliseconds(5000)
                //错误率达到50开启熔断保护
                .withCircuitBreakerErrorThresholdPercentage(50)
                //最大并发请求量
                .withExecutionIsolationSemaphoreMaxConcurrentRequests(10)
                //意味着信号量最多允许执行fallback的并发数为10,超过10 报fallback execution rejected
                .withFallbackIsolationSemaphoreMaxConcurrentRequests(10);

        HystrixCommand.Setter setter = HystrixCommand.Setter.
                withGroupKey(HystrixCommandGroupKey.Factory.asKey("SofaSemaphoreHystrixCommand"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("sofaBootService"))
                .andCommandPropertiesDefaults(hystrixCommand)
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("sofa-hystrix-thread"));

        return setter;
    }

    /**
     * 受保护的资源
     * @return
     * @throws Exception
     */
    @Override
    protected Object run() throws Exception {
        return restTemplate.getForObject("http://HELLOSOFABOOTSERVICE/hello",String.class);
    }

    /**
     * 失败执行的保护方法
     * @return
     */
    @Override
    protected Object getFallback() {
        return "this is fail back policy";
    }
}

同样使用 CountDownLatch 来模拟并发。在 HystrixRibbonController 中添加 testSemaphore 资源方法:

1
2
3
4
5
6
7
8
9
@RequestMapping("/testSemaphore")
    public String testSemaphore(){
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < THREAD_NUM; i ++) {
            new Thread(new ConsumerSemaphore(countDownLatch)).start();
        }
        countDownLatch.countDown();
        return "data";
    }

内部定义一个内部类 ConsumerSemaphore ,模拟调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ConsumerSemaphore implements Runnable {
        private final CountDownLatch startLatch;
        public ConsumerSemaphore(CountDownLatch startLatch) {
            this.startLatch = startLatch;
        }
        @Override
        public void run() {
            try {
                // 线程等待
                startLatch.await();
                // 执行操作
                SofaSemaphoreHystrixCommand sofaThreadPoolHystrixCommand = new SofaSemaphoreHystrixCommand(restTemplate);
                System.out.println(sofaThreadPoolHystrixCommand.execute().toString());

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

结果和线程隔离的差不多。不贴结果了。

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324124154&siteId=291194637