一、雪崩效应
“雪崩效应”: 因 '服务提供者' 的不可用导致 '服务调用者' 的不可用,并将不可用逐渐放大的过程
二、Hystrix介绍
Spring Cloud中服务之间的调用方式主要有两种,一种是Ribbon+RestTemplate,一种是Feign声明式服务调用,在实际项目中,为了服务高可用,一个服务通常会集群部署,运行多个实例, 由于网络原因或者服务自身原因,被调用的服务并不能保证100%请求成功,如果这时候有大量的请求请求这个故障的服务,由于服务之间的依赖关系,故障会进行蔓延,这时候会导致调用服务自身也出现不可用的情况,使用Hystrix可以解决这个问题。当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
三、准备工作:
本文同样有三个工程,分别是:
eureka-server: 服务注册中心,端口1111;
hystrix-service:服务提供者,端口2222和3333,需要启动多个实例;
ribbon-hystrix: Ribbon服务容错,端口4444
feign-hystrix: Feign服务容错,端口5555
对于eureka-server以及hystrix-service的搭建本文不做详细介绍,需要注意的是hystrix-service需要暴露一个接口/getInfo给外部调用:
/**
* @Title: HystrixServiceController
* @Description: 服务提供者
* @Author WeiShiHuai
* @Date 2018/9/11 9:31
*/
@RestController
public class HystrixServiceController {
private static Logger logger = LoggerFactory.getLogger(HystrixServiceController.class);
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/getInfo")
public String getInfo(@RequestParam("name") String name) {
ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
String host = serviceInstance.getHost();
Integer port = serviceInstance.getPort();
String info = "hello, name = " + name + ", host = " + host + ", port = " + port;
logger.info(info);
return info;
}
}
四、新建springcloud_ribbon_hystrix项目
首先讲一下在Ribbon项目中使用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>
<groupId>com.springcloud.wsh</groupId>
<artifactId>springcloud_ribbon_hystrix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud_ribbon_hystrix</name>
<description>Spring Cloud Hystrix Ribbon服务单元</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR6</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-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</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>
</project>
五、启动类加上@EnableCircuitBreaker注解
该注解主要是打开Hystrix断路器功能
/**
* @Description: 应用程序启动类
* @Author: WeiShiHuai
* @Date: 2018/9/11 9:37
* Spring Cloud中服务之间的调用方式主要有两种,一种是Ribbon+RestTemplate,一种是Feign声明式服务调用,在实际项目中,为了服务高可用,一个服务通常会集群部署,运行多个实例,
* 由于网络原因或者服务自身原因,被调用的服务并不能保证100%请求成功,如果这时候有大量的请求请求这个故障的服务,由于服务之间的依赖关系,故障会进行蔓延,这时候会导致调用服务自身也出现不可用的情况,使用Hystrix可以解决这个问题
* 当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个错误响应,而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
* <p>
* “雪崩效应”: 因 '服务提供者' 的不可用导致 '服务调用者' 的不可用,并将不可用逐渐放大的过程
*/
@SpringBootApplication
@EnableDiscoveryClient
// @EnableHystrix 与 @EnableCircuitBreaker注解用于开启Hystrix断路器功能
//@EnableHystrix
@EnableCircuitBreaker
//@EnableHystrixDashboard注解开启Hystrix仪表盘功能,监控请求情况
//通过Hystrix Dashboard可以直接看到各个Hystrix Command的请求响应时间,请求成功率等数据
@EnableHystrixDashboard
//@SpringCloudApplication = @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class SpringcloudRibbonHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudRibbonHystrixApplication.class, args);
}
/**
* 开启Ribbon负载均衡能力,并注入spring容器
*/
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
六、新建HystrixService
- 改造原来的服务消费方式,在使用ribbon消费服务的函数上增加
@HystrixCommand
注解来指定回调方法。
/**
* @Title: HystrixService
* @ProjectName springcloud_hystrix
* @Description: 实现Hystrix断路器
* @Author WeiShiHuai
* @Date 2018/9/11 9:52
*/
@Service
public class HystrixService {
private static Logger logger = LoggerFactory.getLogger(HystrixService.class);
@Autowired
RestTemplate restTemplate;
//@HystrixCommand注解主要对getInfo()开启熔断器的功能,并指定fallbackMethod熔断方法(服务不可用时执行熔断方法)
@HystrixCommand(fallbackMethod = "getInfoErrorFallBack")
public String getInfo(String name) {
String info = restTemplate.getForObject("http://hystrix-service/getInfo?name={name}", String.class, name);
logger.info(info);
return info;
}
/**
* Hystrix熔断方法(即调用失败回调方法)
*
* @param name
* @return
*/
public String getInfoErrorFallBack(String name) {
return "sorry, " + name + ", the hystrix service is not available! ";
}
}
七、新建HystrixController
注入HystrixService,调用hystrixService.getInfo()接口进行断路器测试:
/**
* @Title: HystrixController
* @ProjectName springcloud_hystrix
* @Description: 测试
* @Author WeiShiHuai
* @Date 2018/9/11 9:48
*/
@RestController
public class HystrixController {
private static Logger logger = LoggerFactory.getLogger(HystrixController.class);
@Autowired
private HystrixService hystrixService;
@RequestMapping("/getInfo")
public String getInfo(@RequestParam("name") String name) {
return hystrixService.getInfo(name);
}
}
八、启动项目
依次启动eureka-server端口1111、hystrix-service可以启动两个实例2222和3333,ribbon-hystrix端口4444,
浏览器访问http://localhost:4444/getInfo?name=weixiaohuai,如下图:
接口成功调用,同时看后台打印的日志,也实现了服务的负载均衡调用
此时,我们关闭hystrix-service端口分别为2222以及3333的服务,再次访问http://localhost:4444/getInfo?name=weixiaohuai,如下图:
可以看到,当hystrix-service服务不可用时,我们访问其中的接口,执行了我们制定的fallback熔断方法。这样在Ribbon中使用Hystrix断路器功能已经实现。接下来看一下在Feign中使用Hystrix,因为Feign默认集成了Hystrix,因此不需要添加Hystrix的依赖。
九、新建springcloud_feign_hystrix工程
pom.xml只需要引入主要的Feign依赖:
<?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>
<groupId>com.sprincloud.wsh</groupId>
<artifactId>springcloud_feign_hystrix</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud_feign_hystrix</name>
<description>Spring Cloud Feign Hystrix</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Camden.SR6</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-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</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>
</project>
十、启动类加上@EnableFeignClients注解
该注解主要是打开Feign远程服务调用的功能
/**
* @Description: 应用程序启动类
* @Author: WeiShiHuai
* @Date: 2018/9/11 10:23
*
* Feign默认集成了Hystrix,所以只需要加入Feign的依赖即可。
* Feign是通过@FeignClient()中指定fallback来实现Hystrix断路器功能的,当远程服务调用失败的时候就会执行这个回调方法。
*
*/
@SpringBootApplication
@EnableDiscoveryClient
//开启Feign声明式服务调用功能
@EnableFeignClients
public class SpringcloudFeignHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudFeignHystrixApplication.class, args);
}
}
新建配置文件application.yml:
server:
port: 5555
spring:
application:
name: feign-hystrix
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
十一、新建Feign接口HystrixFeign
/**
* @Title: HystrixFeign
* @ProjectName springcloud_hystrix
* @Description:
* @Author WeiShiHuai
* @Date 2018/9/11 10:25
*/
@FeignClient(value = "hystrix-service", fallback = HystrixFeignFallback.class)
public interface HystrixFeign {
@RequestMapping("/getInfo")
String getInfo(@RequestParam("name") String name);
}
在feign接口中,我们通过指定fallback来实现Hystrix服务容错功能
新建HystrixFeignFallback回调类,并且实现@FeignClient修饰的接口:
/**
* @Title: HystrixFeignFallback
* @ProjectName springcloud_feign_hystrix
* @Description: FeignClient失败回调方法
* @Author WeiShiHuai
* @Date 2018/9/11 10:28
* FeignClient失败回调方法必须实现使用@FeignClient标识的接口(implements HystrixFeign),实现其中的方法
*/
@Component
public class HystrixFeignFallback implements HystrixFeign {
/**
* 由于某种原因使得服务调用不成功时会执行该回调方法
*
* @param name
* @return
*/
@Override
public String getInfo(String name) {
return "sorry " + name + ", feign client error";
}
}
十二、新建FeignHystrixController
暴露一个getInfo接口给外部调用,如下:
/**
* @Title: HystrixFeignController
* @ProjectName springcloud_feign_hystrix
* @Description: 测试Feign
* @Author WeiShiHuai
* @Date 2018/9/11 10:28
* 注入FeignClient,调用feignClient的方法实现远程方法调用
*/
@RestController
public class HystrixFeignController {
private static Logger logger = LoggerFactory.getLogger(HystrixFeignController.class);
@Autowired
private HystrixFeign hystrixFeign;
/**
* 使用http://localhost:5555/getInfo?name=xxx访问,实际上会通过FeignClient调用服务hystrix-service提供的getInfo接口
*
* @param name
* @return
*/
@GetMapping("/getInfo")
public String getInfo(@RequestParam("name") String name) {
String info = hystrixFeign.getInfo(name);
logger.info(info);
return info;
}
}
接下来,同样启动eureka-server、以及hystrix-service两个实例,还有启动feign-hystrix,访问http://localhost:5555/getInfo?name=weixiaohuai,结果如下:
接口已经成功调用,通过查看后台日志,同样feign也实现对服务的负载均衡调用
此时,我们停掉hystrix-service的两个实例,再次访问http://localhost:5555/getInfo?name=weixiaohuai,服务会进行熔断处理,执行了我们指定的fallback方法。
由此证明Hystrix断路器起作用了。
十三、总结
以下内容,摘自网上总结:
雪崩效应的原因: 1) 服务提供者不可用 a.硬件故障 b.程序Bug c.用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求造成服务提供者的不可用 2) 重试加大流量 a.用户重试:用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单 b.代码逻辑重试:服务调用端的会存在大量服务异常后的重试逻辑 3) 服务调用者不可用 a.同步等待造成的资源耗尽:使用 同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 造成服务雪崩效应产生 雪崩效应的解决措施: 1) 流量控制 a.网关限流 因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制, 由此而来的OpenResty也越来越热门. b.用户交互限流 具体措施: a21. 采用加载动画,提高用户的忍耐等待时间. a22. 提交按钮添加强制等待时间机制. c.关闭重试 2) 改进缓存模式 a.缓存预加载 b.同步改为异步刷新 3) 服务自动扩容 a.AWS的auto scaling 4) 服务调用者降级服务 a.资源隔离:主要是对调用服务的线程池进行隔离. b.对依赖服务进行分类 依赖服务分为: 强依赖和若依赖. 强依赖服务不可用会导致当前业务中止,而弱依赖服务的不可用不会导致当前业务的中止. c.不可用服务的调用快速失败 一般通过 超时机制, 熔断器 和熔断后的 降级方法 来实现