什么是雪崩效应
在微服务架构中, 我们将系统拆分成了很多服务单元, 各单元的应用间通过服务注册与订阅的方式互相依赖。 由于每个单元都在不同的进程中运行, 依赖通过远程调用的方式执行, 这样就有可能因为网络原因或是依赖服务自身间题出现调用故障或延迟, 而这些问题会直接导致调用方的对外服务也出现延迟, 若此时调用方的请求不断增加, 最后就会因等待出现故障的依赖方响应形成任务积压,最终导致自身服务的瘫痪。在微服务架构中, 存在着那么多的服务单元, 若一个单元出现故障, 就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构相较传统架构更加不稳定。
什么是断路器
在分布式架构中, 当某个服务单元发生故障(类似用电器发生短路) 之后, 通过断路器的故障监控(类似熔断保险丝), 会向调用方返回一个我们自定义的错误响应, 而不是长时间的等待。 这样就不会使得线程因服务调用故障被长时间占用不释放,避免了故障在分布式系统中的蔓延。
调用失败达到一个特定的阀值(5秒之内发生20次失败是Hystrix定义的缺省值), 断路器就会被处于open状态, 之后对服务的调用都不会执行原有方法, 取而代之处理请求的是由断路器提供的一个Fallback消息。 Hystrix提供了相应机制,可以让开发者定义这个Fallbak消息。
这个fallback可以是另外一个Hystrix保护的调用,或者静态数据,或者合法的空值,Fallbacks还可以组成链式结构。
断路器的功能
Spring Cloud Hystrix实现了断路器、 线程隔离等一系列服务保护功能。它也是基于Netflix的开源框架Hystrix实现的, 该框架的目标在于通过控制那些访问远程系统、 服务和第三方库的节点, 从而对延迟和故障提供更强大的容错能力。Hystrix具备服务降级、 服务熔断、 线程和信号隔离、 请求缓存、 请求合并以及服务监控等强大功能。
创建一个Hystrix实例
因为断路器一般和客户端的负载均衡一起使用的,所以我们在上一个实例的基础上进行改造。
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>ribbon.consumer</groupId>
<artifactId>ribbon-consumer</artifactId>
<version>1.0</version>
<name>ribbon-consumer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
主类添加注解:
@EnableDiscoveryClient
// 让该应用注册为 Eureka 客户端应用
@EnableCircuitBreaker
// 开启断路器功能
@SpringBootApplication
// @SpringCloudApplication
// 该注解中包含了上述我们所引用的三个注解, 这也意味着—个 Spring Cloud 标准应用应包含服务发现以及断路器。
package ribbon.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@EnableDiscoveryClient
// 让该应用注册为 Eureka 客户端应用
@EnableCircuitBreaker
// 开启断路器功能
@SpringBootApplication
// @SpringCloudApplication
// 该注解中包含了上述我们所引用的三个注解, 这也意味着—个 Spring Cloud 标准应用应包含服务发现以及断路器。
public class RibbonConsumerApplication {
@Bean
@LoadBalanced
// 开启客户端负载均衡。
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonConsumerApplication.class, args);
}
}
application.properties
定义服务名,端口号,和服务中心的url,向服务中心注册自己,并定时获取服务列表。
spring.application.name=ribbon-consumer
server.port=9000
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
创建service
我们可以看到,调用其他服务的方法,放在了service层,并且被断路器监控。
@HystrixCommand(fallbackMethod = "helloFallback")
而且在断路器的注解中,还指定了调用失败后的fallback方法。
package ribbon.consumer.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "helloFallback")
// 注解来指定回调方法
public String helloService() {
// 远程调用服务,是通过在注册中心的服务名称调用而不是IP
return restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class).getBody();
}
public String helloFallback() {
return "error";
}
}
创建Controller
package ribbon.consumer.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import ribbon.consumer.service.HelloService;
@RestController
public class ConsumerController {
@Autowired
HelloService helloService;
@RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET)
public String helloConsumer() {
return helloService.helloService();
}
}
以上实例完成,需要同时启动服务中心和至少2个服务提供者(服务名称为HELLO-SERVICE且定义了一个接口名为hello)。可以通过cmd命令 java -jar XXXX.jar --serve.port=XXXX,来设置不同的端口号,模拟多服务。
测试结果
当我们启动断路器服务,服务中心,和两个HELLO-SERVICE服务时,可以在服务中心的注册列表看到这三个服务。由于Ribbon客户端负载均衡默认的配置是轮询访问,所以我们多次请求 http://localhost:9000/ribbon-consumer时,请求会轮流发给两个HELLO-SERVICE去处理,然后我们关闭一个HELLO-SERVICE服务后,再多次请求http://localhost:9000/ribbon-consumer时,会看到断路器生效了,一次请求成功返回正确输出结果,一次请求会失败,返回断路器定义的fallback方法,输出erro。而且当请求超时,超过断路器的设置后,也会触发断路器,执行断路器的返回方法。