文章目录
前言:
Spring Cloud系列教程的所有博客均在下方的目录链接中,方便大家查找和阅读。建议按照顺序学习,对于项目搭建有疑问的可以着重看目录里的第二篇博客。
Spring cloud学习专栏目录
一、当前架构问题分析
1.1 问题分析
我们当前的架构如下图所示,使用Eureka进行服务发现服务注册,客户端使用Ribbon进行负载均衡。现在我们假设一种情况,如果服务提供者的响应非常缓慢,那么服务消费者对服务提供者的请求就会被强制等待,直到http请求超时,然后抛出异常。那么在高负载场景下,如果不做任何处理,这种问题很可能造成所有处理用户请求的线程都被耗竭,而不能响应用户的进一步请求。
1.2 雪崩效应
在微服务架构中通常会有多个服务层调用,大量的微服务通过网络进行通信,从而支撑起整个系统。各个微服务之间也难免存在大量的依赖关系。然而任何服务都不是100%可用的,网络往往也是脆弱的,所以难免有些请求会失败。基础服务的故障导致级联故障,进而造成了整个系统的不可用,这种现象被称为服务雪崩效应。服务雪崩效应描述的是一种因服务提供者的不可用导致服务消费者的不可用,并将不可用逐渐放大的过程。
如下图所示,A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。
1.3 解决方案
1.3.1 超时机制
通过网络请求其他服务时,都必须设置超时时间。正常情况下,一个远程调用一般在几十毫秒内就返回了。当依赖的服务不可用,或者因为网络问题,响应时间将会变得很长(几十秒)。而通常情况下,一次远程调用对应了一个线程/进程,如果响应太慢,那这个线程/进程就会得不到释放。而线程/进程都对应了系统资源,如果大量的线程/进程得不到释放,并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用。所以必须为每个请求设置超时。
1.3.2 断路器模式
断路器模式我们可以把它类比成家中的电闸,如果没有电闸当电流过载了(如功率过大、短路等),如果电路不断开,电路就会升温,造成电路烧坏或是起火等危险。有了电闸之后,当电流过载时,会自动跳闸,从而保护整条电路的安全。当电流过载的问题被解决后,只要将关闭电闸,电路就又可以正常工作了。
同理在服务器中,当依赖的服务(服务提供者)出现大量超时的情况,再让新的请求去访问已经没有太大意义,只会消耗现有的资源。譬如我们设置了超时时间为1秒,如果短时间内有大量的请求(譬如50个)在1秒内都得不到响应,往往就意味着异常。此时就没有必要让更多的请求去访问这个依赖了,我们应该使用断路器避免资源浪费。
断路器可以实现快速失败,如果它在一段时间内侦测到许多类似的错误(譬如超时),就会强迫其以后的多个调用快速失败,不再请求所依赖的服务,从而防止应用程序不断地尝试执行可能会失败的操作,这样应用程序可以继续执行而不用等待修正错误,或者浪费CPU时间去等待长时间的超时。断路器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。
断路器包含三种状态打开、半开和关闭。
正常情况下处于关闭状态,如果发生异常断路器打开。断路器打开一定时间后,会进入半开状态去检测服务是否恢复正常,如果没有恢复就回来打开状态,在经过一定的时间在恢复为半开状态。如果检测到服务正常了,就变成关闭状态。
二、Hystrix简介
2.1 Hystrix是什么:
Hystrix是一个延迟和容错库,可通过添加延迟容限和容错逻辑来控制分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
2.2 Hystrix的作用:
- 通过第三方客户端库提供保护,并控制延迟和失败依赖项的故障。
- 停止复杂的分布式系统中的级联故障。
- 快速失败,迅速恢复。
- 回退并在可能的情况下正常降级。
- 启用近乎实时的监视,警报和操作控制。
2.3 Hystrix实现原理:
- 包裹请求:将对外部系统(或“依赖项”)的所有调用包装在通常在单独线程中执行的HystrixCommand或HystrixObservableCommand对象中(这是命令模式的示例)。
- 资源隔离:为每个依赖项维护一个小的线程池(或信号灯);如果已满,发往该依赖项的请求将立即被拒绝,而不是排队也就是快速失败。
- 实时监控:几乎实时监控指标和配置更改。测量成功,失败(客户端抛出的异常),超时和线程拒绝。
- 断路器:如果某个服务的错误百分比超过阈值,则使断路器跳闸,以在一段时间内手动或自动停止所有对特定服务的请求。
- 回退机制:当请求失败,被拒绝,超时或短路时执行回退逻辑。
- 自我修复:断路器打开一定时间后,会进入半开状态去检测服务是否恢复正常,如果没有恢复就回来打开状态,在经过一定的时间在恢复为半开状态。如果检测到服务正常了,就变成关闭状态。
三、Hystrix 实战——为服务消费者添加Hystrix
3.1 pom文件添加依赖
找到服务消费者的父工程的pom文件,添加hystrix的依赖。(对于项目搭建有疑问的读者,请看我spring cloud专栏里的先前博客)
<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 https://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.0.3.RELEASE</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>microservice-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>microservice-provide-user</module>
<module>microservice-consumer-ticket</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- 导入Spring Cloud的依赖管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 整合eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--整合hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 资源文件拷贝插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- springboot插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2 修改服务消费者(车票微服务)的service方法
3.2.1 添加注解@HystrixCommand
在需要进行容错处理的方法上添加注解
3.2.2 编写处理错误的fallback方法
注意方法名要和@HystrixCommand注解中定义的方法名保持一致,而且返回值和传参也要和原方法保持一致。
package com.spring.ticketservice.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.spring.ticketservice.pojo.User;
@Service
public class TicketService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
/*
* 查询车票信息时查询用户的信息(这里为了简化操作就不对车票信息进行查询,直接去调用查询用户信息的接口)
*/
@HystrixCommand(fallbackMethod = "queryTicketInfoFallbackMethod")
public User queryTicketInfo(Long id) {
String serviceId = "microservice-provide-user";
ServiceInstance serviceInstance = this.loadBalancerClient.choose(serviceId);
//打印出请求了哪个端口的用户微服务
System.out.println("请求了" + serviceInstance.getPort());
//向用户微服务中的接口发送请求的地址
//String url = "http://localhost:8081/getUserInfo/"+ id;
String url = "http://microservice-provide-user/getUserInfo/"+ id;
return restTemplate.getForObject(url, User.class);
}
/*
* 处理请求失败的方法
*/
public User queryTicketInfoFallbackMethod(Long id) {
User user = new User(0l,"出错了");
return user;
}
}
3.3 修改服务消费者(车票微服务)的启动类
添加注解@EnableCircuitBreaker
package com.spring.ticketservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import com.spring.config.TestConfiguration;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "microservice-provide-user",configuration = TestConfiguration.class)
@EnableCircuitBreaker
public class TicketServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TicketServiceApplication.class, args);
}
/*
* 向Spring容器中添加RestTemplate对象
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.4 启动服务进行测试
我们启动Eureka服务、用户微服务、车票微服务。
访问eureka可以看到两个服务启动成功,开始测试
———————————————————————————————————
访问车票微服务的接口 http://localhost:8082/getTicketInfo/1,可以看到现在是正常的情况。
———————————————————————————————————
关闭用户微服务,再次访问该接口可以看到执行力我们自定义的fallback方法,返回了一个name为出错了的user对象。说明hystrix发挥了作用,
四、Hystrix的Health Indicator及Metrics Stream
4.1 pom文件添加依赖
找到服务消费者的父工程的pom文件,添加健康监控的依赖
<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 https://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.0.3.RELEASE</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>microservice-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>microservice-provide-user</module>
<module>microservice-consumer-ticket</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- 导入Spring Cloud的依赖管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 整合eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--整合hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 健康监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 资源文件拷贝插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- springboot插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 修改服务消费者(车票微服务)的application.properties配置文件
默认情况下actuator/health节点应该是开放的,但是在我测试的过程中发现访问会404报错,于是我就加上了management.endpoints.web.exposure.include=*来开放所有的节点。
如果你只想看到hystrix 断路器的状态那么就不用配置查看详细的健康信息management.endpoint.health.show-details=always。
# 配置api端口号
server.port=8082
# tomcat
server.tomcat.uri-encoding=UTF-8
# 服务名称,也就是在eureka
spring.application.name=microservice-consumer-ticket
# 是否启动注册,这是一个客户端需要注册
eureka.client.register-with-eureka=true
# 是否检索服务
eureka.client.fetch-registry=true
# 服务注册中心的地址
eureka.client.service-url.default-zone=http://localhost:8761/eureka
#eureka.client.service-url.default-zone=http://admin:123456@localhost:8761/eureka
#开放所有的节点
management.endpoints.web.exposure.include=*
#显示详细的健康信息
management.endpoint.health.show-details=always
4.3 启动服务进行测试
启动eureka微服务,车票微服务。
4.3.1 测试Health Indicator
访问车票微服务的端口后加上actuator/health,可以看到打印出了详细的健康信息,包括服务本身的运行情况、断路器是否打开等等。
4.3.2 测试Metrics Stream
接下来我们访问 actuator/hystrix.stream来看一下结果,可以看到左边一直出现ping:。Metrics Stream其实就是一个对于api接口的监控,监控每一个接口的状态,可以看到他是一直在刷新的。
———————————————————————————————————
我们再来做进一步的测试,访问车票微服务中的接口,然后我们在回到刚才的页面,可以看到出现了很多信息,说明他在进行监控。
五、Hystrix Dashboard
通过actuator/hystrix.stream可以实时看到最新的监控数据,但是看起来很不方便。所有spring cloud提供了一个Hystrix Dashboard,可以以图形化界面展示这些数据方便我们的阅读。
5.1 pom文件添加依赖
找到服务消费者(车票微服务)的父工程的pom文件,添加dashboard的依赖。
<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 https://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.0.3.RELEASE</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>microservice-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>microservice-provide-user</module>
<module>microservice-consumer-ticket</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- 导入Spring Cloud的依赖管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--springboot 整合eureka客户端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--整合hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 健康监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix 仪表盘 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 资源文件拷贝插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- springboot插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5.2 修改服务消费者的启动类
添加@EnableHystrixDashboard注解
package com.spring.ticketservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import com.spring.config.TestConfiguration;
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "microservice-provide-user",configuration = TestConfiguration.class)
@EnableCircuitBreaker
@EnableHystrixDashboard
public class TicketServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TicketServiceApplication.class, args);
}
/*
* 向Spring容器中添加RestTemplate对象
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
5.3 启动服务进行测试
启动eureka服务、车票微服务、用户微服务。
浏览器访问车票微服务的地址后加/hystrix http://localhost:8082/hystrix可以看到来到了Hystrix Dashboard的首页。
———————————————————————————————————
输入地址 http://localhost:8082/actuator/hystrix.stream 以及名称后点击Monitor Stream
———————————————————————————————————
进入到如下页面,可以看到这里有接口queryTicketInfo的相关信息。
———————————————————————————————————
访问车票微服务中的接口数次(这里6次)可以看到页面里的数值发生了变化, 说明他在实时的监控。
下图为各项数值所代表的含义