系统设计

Spring Cloud 学习笔记 - No.4 断路器 Hys

2018-07-09  本文已影响176人  专职跑龙套

请先阅读之前的内容:

在微服务架构中,各单元应用间通过服务注册与订阅的方式互相依赖。
由于每个单元都在不同的进程中运行,依赖通过远程调用的方式执行,这样就有可能因为网络原因或是依赖服务自身问题出现调用故障延迟,而这些问题会直接导致调用方的对外服务也出现延迟,若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,线程资源无法释放,最终导致自身服务的瘫痪,进一步甚至出现故障的蔓延最终导致整个系统的瘫痪。

Spring Cloud Netflix Hystrix 介绍

https://spring.io/guides/gs/circuit-breaker/

针对上述问题,在 Spring Cloud Hystrix 中实现了线程隔离、断路器等一系列的服务保护功能,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

一个需要断路器的场景

场景描述:在之前的例子中,我们的 eureka-consumer 调用 eureka-client 来提供加法服务,eureka-client 启动了两个实例,端口分别是 2001 和 2002。

对于 eureka-client 提供的加法服务,可能会出现如下的情况:

@GetMapping("/add")
public Integer add(@RequestParam Integer operand1, @RequestParam Integer operand2)  throws InterruptedException {

    Thread.sleep(5000L);

    return operand1 + operand2;
}

调用 eureka-consumerconsumer 接口 http://127.0.0.1:3001/consumer,会出现如下的结果:

调用超时
从后台日志中可以看出调用超时:
调用超时
因为 Feign 默认的 read timeout 就是5秒,参见 https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html

这样会有一个问题:每一个请求都至少需要执行5秒,这样会出现请求堆积的情况。
我们希望:如果在某一个时间窗口内,超过一定数量的请求发生了超时,我们启动某个机制,使得后来的请求直接返回,不需要再次等待5秒。这就是断路器。

Hystrix 服务降级

我们利用之前创建的服务消费者 eureka-consumer
首先在 pom.xml 中增加如下的依赖:

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

注意,如果是 Finchley 版本的 Spring Cloud,需要再添加如下依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.7.1</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
</dependency>

否则,启动时会报如下的错误:

ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.0.3.RELEASE:run (default-cli) on project eureka-consumer: An exception occurred while running. null: InvocationTargetException: Error creating bean with name 'hystrixCommandAspect' defined in class path resource [org/springframework/cloud/netflix/hystrix/HystrixCircuitBreakerConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect]: Factory method 'hystrixCommandAspect' threw exception; nested exception is java.lang.NoClassDefFoundError: org/aspectj/lang/JoinPoint: org.aspectj.lang.JoinPoint -> [Help 1]

随后在应用主类中使用 @EnableCircuitBreaker@EnableHystrix 注解开启 Hystrix 的使用:

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class EurekaConsumerApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EurekaConsumerApplication.class, args);
    }
}

随后改造服务消费方式,新增 ConsumerService 类,然后将在 ConsumerController 中的逻辑迁移过去。最后,在为具体执行逻辑的函数上增加 @HystrixCommand注解来指定服务降级方法

@Service
public class ConsumerService {
    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1000"),
                    @HystrixProperty(name = "execution.isolation.strategy",value = "THREAD")},
            fallbackMethod = "fallback")
    public String consumer() {
        // 调用加法服务
        String url = "http://eureka-client/add";

        UriComponentsBuilder builder = UriComponentsBuilder
                .fromUriString(url)
                // Add query parameter
                .queryParam("operand1", 1)
                .queryParam("operand2", 2);

        return restTemplate.getForObject(builder.toUriString(), Integer.class)+"";
    }

    public String fallback() {
        return "fallback";
    }

}
@RestController
public class ConsumerController {

    private final static Logger logger = LoggerFactory.getLogger(ConsumerController.class);

    @Autowired
    private ConsumerService consumerService;

    @GetMapping("/consumer")
    public String consumer() {
        return consumerService.consumer();
    }

}

此时,再次调用 eureka-consumerconsumer 接口 http://127.0.0.1:3001/consumer,会出现如下的结果:

服务降级

Hystrix 依赖隔离

Hystrix 会为每一个 Hystrix 命令创建一个独立的线程池,这样就算某个在 Hystrix 命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。

在上面的示例中,我们使用了@HystrixCommand 来将某个函数包装成了 Hystrix 命令,这里除了定义服务降级之外,Hystrix 框架就会自动的为这个函数实现调用的隔离。所以,依赖隔离、服务降级在使用时候都是一体化实现的

Hystrix 断路器

在上面的示例中,当我们把服务提供者 eureka-client 中加入了模拟的时间延迟之后,在服务消费端的服务降级逻辑因为 Hystrix 命令调用依赖服务超时,触发了降级逻辑,但是即使这样,受限于 Hystrix 超时时间的问题,我们的调用依然很有可能产生堆积。

断路器的三个重要参数:

断路器未打开之前,对于之前那个示例的情况就是每个请求都会在当 Hystrix 超时之后返回 fallback,每个请求时间延迟就是近似 Hystrix 的超时时间,如果设置为5秒,那么每个请求就都要延迟5秒才会返回。

断路器打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就不会等待5秒之后才返回 fallback。
通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

在断路器打开之后,处理逻辑并没有结束,我们的降级逻辑已经被成了主逻辑,那么原来的主逻辑要如何恢复呢?对于这一问题,Hystrix 也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,Hystrix 会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

Hystrix 监控面板

在上面的示例中,断路器是根据一段时间窗内的请求情况来判断并操作断路器的打开和关闭状态的。
而这些请求情况的指标信息都是 HystrixCommandHystrixObservableCommand 实例在执行过程中记录的重要度量信息,它们除了 Hystrix 断路器实现中使用之外,对于系统运维也有非常大的帮助。
这些指标信息会以“滚动时间窗”与“桶”结合的方式进行汇总,并在内存中驻留一段时间,以供内部或外部进行查询使用,Hystrix Dashboard 就是这些指标内容的消费者之一。

hystrix-dashboard 监控面板

可以通过如下的 Spring Assistant 插件来创建项目 hystrix-dashboard,添加 Hystrix Dashboard 等作为依赖。

hystrix-dashboard 的创建
hystrix-dashboard 的创建
hystrix-dashboard 的创建
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

为应用主类加上 @EnableHystrixDashboard,启用 Hystrix Dashboard 功能:

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {

    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardApplication.class, args);
    }
}

application.properties 文件配置如下:

spring.application.name=hystrix-dashboard
server.port=5001

最后通过命令 mvn spring-boot:run 启动该项目,访问 http://127.0.0.1:5001/hystrix 可以看到:

Hystrix Dashboard 监控首页

Hystrix Dashboard 共支持三种不同的监控方式,依次为:

我们这里先看第三种:单体应用的监控,例如我们想监控上面的例子 eureka-consumer,它对应的监控数据接口为 http://127.0.0.1:3001/actuator/hystrix.stream

注意:

management.endpoints.web.exposure.include=*

随后通过如下的方式创建一个针对 eureka-consumer 的监控面板

创建一个针对 eureka-consumer 的监控面板
创建一个针对 eureka-consumer 的监控面板

我们可以在监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线。

其他一些数量指标

此时的系统架构如下:图片引自 http://blog.didispace.com/spring-cloud-starter-dalston-5-2/

监控 单个实例 系统架构

Hystrix 监控数据聚合

上面的例子中,只能实现对服务 单个实例 的数据展现,在生产环境我们的服务是肯定需要做高可用的,那么对于 多个实例 的情况,我们就需要将这些度量指标数据进行聚合。

这里需要另外一个工具:Turbine。
此时的系统架构如下:图片引自 http://blog.didispace.com/spring-cloud-starter-dalston-5-2/

监控 多个实例 系统架构

可以通过如下的 Spring Assistant 插件来创建项目 turbine,添加 Hystrix Dashboard 等作为依赖。

turbine 的创建
turbine 的创建
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>

为应用主类加上 @EnableTurbine 注解开启 Turbine:

@SpringBootApplication
@EnableTurbine
@EnableDiscoveryClient
public class TurbineApplication {

    public static void main(String[] args) {
        SpringApplication.run(TurbineApplication.class, args);
    }
}

application.properties 加入eureka和turbine的相关配置:

spring.application.name=turbine
server.port=6001
management.port=6002

eureka.client.serviceUrl.defaultZone=http://localhost:1234/eureka/

turbine.app-config=eureka-consumer
turbine.cluster-name-expression="default"
turbine.combine-host-port=true

management.endpoints.web.exposure.include=urbine.stream

最后通过命令 mvn spring-boot:run 启动该项目,访问 http://127.0.0.1:6001/turbine.stream 可以看到 ping 的结果:

http://127.0.0.1:6001/turbine.stream

我们可以将 http://127.0.0.1:6001/turbine.stream 配置到 Hystrix Dashboard 中:

将集群的监控配置到 Hystrix Dashboard

我们也可以通过消息代理收集聚合,此时的架构如下:图片引自 http://blog.didispace.com/spring-cloud-starter-dalston-5-2/

通过消息代理收集聚合
具体的使用,参见 http://blog.didispace.com/spring-cloud-starter-dalston-5-2/

引用:
程序猿DD Spring Cloud基础教程
Spring Cloud构建微服务架构:服务容错保护(Hystrix服务降级)【Dalston版】
Spring Cloud构建微服务架构:服务容错保护(Hystrix依赖隔离)【Dalston版】
Spring Cloud构建微服务架构:服务容错保护(Hystrix断路器)【Dalston版】
Spring Cloud构建微服务架构:Hystrix监控面板【Dalston版】
Spring Cloud构建微服务架构:Hystrix监控数据聚合【Dalston版】
Spring Cloud Dalston中文文档

上一篇 下一篇

猜你喜欢

热点阅读