6-Spring cloud之Hystrix容错处理(上)
- 1. 前言
- 1.1 关于雪崩
- 1.1.1 什么是灾难性雪崩
- 1.1.2 服务雪崩形成的原因
- 1.1.3 如何防止灾难性雪崩效应
- 1.2 前文介绍
- 2. Hystrix容错处理
- 2.1 项目搭建(Ribbon整合Hystrix)
- 2.1.1 项目结构
- 2.1.2 pom文件
- 2.1.3 yml文件
- 2.1.4 配置类
- 2.1.5 启动类
- 2.1.6 controller
- 2.1.7 启动,确保服务可用
- 2.1.8 演示远程服务不可用
- 2.1.8.1 简单演示
- 2.1.8.2 远程服务正常和不正常
- 2.2 服务降级
- 2.2.1 简单说服务降级
- 2.2.2 模拟服务降级
- 2.2.3 看效果
- 2.3 服务熔断
- 2.3.1 简单说服务熔断
- 2.3.2 模拟服务熔断1(停服务)
- 2.3.2.1 降级代码
- 2.3.2.2 看效果
- 2.3.3 模拟服务熔断1(服务端模拟异常)
- 2.3.3.1 修改服务提供者
- 2.3.3.2 看效果
- 2.3.3.2.1 注释掉熔断处理
- 2.3.3.2.2 加上熔断处理
- 2.4 模糊使用(熔断?降级?)
- 2.4.1 代码(服务端代码正常,消费端代码模拟异常)
- 2.4.2 效果
- 2.4.2.1 未开启熔断时效果
- 2.4.2.2 开启熔断后效果
- 3. Feign整合Hystrix容错处理
- 3.1 核心代码
- 3.1.1 实现类和注解
- 3.1.1.1 方式1
- 3.1.1.2 方式2
- 3.1.2 消费者的yml
- 3.1.3 看效果
1. 前言
1.1 关于雪崩
1.1.1 什么是灾难性雪崩
- 微服务之间相互调用,因为调用链中的一个服务故障,导致一系列的服务不可能,即引起整个链路都无法访问的情况。
1.1.2 服务雪崩形成的原因
- 服务提供者不可用。
如:硬件故障、程序bug、缓存击穿、并发请求量过大(比如双十一时)等。
其中,缓存击穿一般发生在缓存应用重启,所有缓存被清空时,以及短时间内大量的缓存失效时。大量的缓存不命中,使请求直接访问后端,造成服务提供者超负荷运行,引起服务不可用。 - 服务调用者不可用。
如:同步请求阻塞造成的资源耗尽。 - 重试加大流量。如:
- 用户重试:
在服务提供者不可用后,用户由于忍受不了界面上的长时间等待,而不断刷新页面,甚至提交表单。 - 代码重试逻辑:
服务的调用端存在大量服务异常后的重试逻辑。
- 用户重试:
1.1.3 如何防止灾难性雪崩效应
- 服务降级
- 服务熔断
- 请求缓存
1.2 前文介绍
- 本文是续接前文,所以下面有些集群或服务都不在介绍,比如Eureka集群还是在前面的基础上启动的,关于前面几篇文章的介绍如下:
- 1-Eureka服务注册与发现以及Eureka集群搭建(实操型).
- 2-Spring cloud之Eureka快速剔除失效服务
- 3-Spring cloud之搭建Ribbon负载均衡——服务器上实操(上)
- 4-Spring cloud之搭建Ribbon负载均衡——服务器上实操(下)
- 5-Spring cloud之Feign的使用——服务器上实操.
2. Hystrix容错处理
2.1 项目搭建(Ribbon整合Hystrix)
2.1.1 项目结构
- 为了整体项目看起来有前后顺序,层次更清晰,所以本项目直接新建一个Module,可以理解为此项目是在dog-consumer-80的升级版,就是简单地在Ribbon上加上了Hystrix。
- 项目结构如下:
2.1.2 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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.liu.susu</groupId> <artifactId>dog-cloud-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>dog-consumer-ribbon-hystrix-80</artifactId> <packaging>jar</packaging> <name>dog-consumer-ribbon-hystrix-80</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>com.liu.susu</groupId> <artifactId>dog-po</artifactId> <version>${project.version}</version> </dependency> <!--版本同${spring-boot.version}--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--引入ribbon相关依赖,ribbon是客户端的负载均衡,ribbon需要和eureka整合--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <!--引入Hystrix相关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
2.1.3 yml文件
-
如下:
server: port: 80 spring: application: name: dog-consumer-ribbon-hystrix eureka: client: # 客户端注册进eureka服务列表内 register-with-eureka: false # false表示不向注册中心注册自己 service-url: defaultZone: http://IP1:2886/eureka/,http://IP2:2886/eureka,http://IP3:2886/eureka/
2.1.4 配置类
- 如下:
2.1.5 启动类
- 如下:
2.1.6 controller
-
如下:
package com.liu.susu.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * @Description * @Author susu */ @RestController public class DogConsumerController { private static final String REST_URL_PREFIX = "http://DOG-PROVIDER"; @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/consumer/dog/hello") public String hello(){ System.out.println("=============================="); String url = REST_URL_PREFIX + "/dog/hello"; return restTemplate.getForObject(url, String.class); } /** * http://localhost:80/consumer/dog/getDogByNum/1 * http://localhost/consumer/dog/getDogByNum/1 */ @RequestMapping("/consumer/dog/getDogByNum/{dogNum}") public Object getDogByNum(@PathVariable("dogNum") Long dogNum){ String url = REST_URL_PREFIX + "/dog/getDogByNum/" + dogNum; return restTemplate.getForObject(url, Object.class); } }
2.1.7 启动,确保服务可用
- 因为Eureka集群和几台服务提供者一直没有停过,所以查看一下,如下:
- 启动新创建的服务消费者,然后访问如下:
- 确保都没问题之后,就可以继续模拟处理服务降级和熔断了,继续……
2.1.8 演示远程服务不可用
2.1.8.1 简单演示
-
现在把那几台服务提供者都停掉(刚停掉的时候,还是在注册中心的,看设置的心跳时间)
-
然后再访问看一下效果(过程变化):
- 先是
Connection refused (Connection refused)
(这种就是服务不可用)
- 然后,再
No instances available
但是这种情况是正常的,因为你把服务已经停了
- 先是
2.1.8.2 远程服务正常和不正常
- 远程服务正常
- 有返回结果
- 有异常传递
- 远程服务错误
- 远程服务不可用(拒绝访问)
- 远程服务响应超时
2.2 服务降级
2.2.1 简单说服务降级
-
服务降级是指在服务出现故障或异常时,为了保证核心服务的稳定性,暂时关闭一些不太重要的服务,或者返回一些简单的缓存数据等,以保证核心服务的正常运行。服务降级能够有效地减少服务的响应时间和错误率,提高系统的可用性。
即:服务降级是针对系统整体资源的合理分配。区分核心服务和非核心服务。对某个服务的访问延迟时间、异常等情况做出预估并给出兜底方法。这是一种全局性的考量,从整体负荷考虑。
2.2.2 模拟服务降级
- 接下来,我们模拟一个超时
- 在未做服务降级处理的时候,如果我们故意设计超时的话,这时候不管超时多久,SpringCloud远程调用时都会等待。自己可以试试,这里就不演示了。
- 看模拟服务超时并做降级处理的核心代码如下:
-
启动类
@EnableHystrix //开启Hystrix容错处理能力
-
controller里方法
@HystrixCommand(fallbackMethod = "errorHello")
/** * @EnableHystrix 启动类上注解,开启Hystrix容错处理能力 * @HystrixCommand 代表当前方法是一个需要做容错处理的方法 * @EnableHystrix 结合 @HystrixCommand,默认地配置了一个远程服务超时配置,默认设置超时是1秒 */ @RequestMapping(value = "/consumer/dog/hello") @HystrixCommand(fallbackMethod = "errorHello") public String hello(){ System.out.println("=============================="); //默认情况下,SpringCloud远程调用时,不管多久都会等 try { Thread.sleep(3000); } catch (InterruptedException e) { throw new RuntimeException(e); } String url = REST_URL_PREFIX + "/dog/hello"; return restTemplate.getForObject(url, String.class); } /** * 降级方法 * 1、返回类型要和对应的服务方法的返回类型一致 * 2、参数和对应的服务方法要一致 * 3、返回的内容:远程服务访问错误时(比如超时),返回的拖底数据 */ public String errorHello(){ return "服务器忙,请稍后再试!"; }
-
2.2.3 看效果
-
如下:
2.3 服务熔断
2.3.1 简单说服务熔断
- 服务熔断是指在服务出现故障时或异常时,通过断开与该服务的连接,避免该服务继续接受请求,从而阻止故障的扩散,并快速恢复服务的可用性。服务熔断通常根据一定的策略来检测服务的可用性,如果服务的响应时间或错误率超过了一定的阈值,就会触发熔断机制,断开与该服务的连接,之后定期重试连接,直至服务正常。
- 即:服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑
2.3.2 模拟服务熔断1(停服务)
2.3.2.1 降级代码
-
如下:
@RequestMapping("/consumer/dog/getDogByNum/{dogNum}") @HystrixCommand(fallbackMethod = "errorGetDogByNum",commandProperties = { //是否开启熔断机制 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED,value = "true"), //一个时间窗内,发生远程访问错误的次数阈值,达到开启熔断 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "3"), //一个时间窗内,发生远程访问错误的百分比,达到则开启熔断 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,value = "20"), //开启熔断后,多少毫秒内,不发起远程服务访问 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "3000") }) public Object getDogByNum(@PathVariable("dogNum") Long dogNum){ System.out.println("本地测试熔断...."); String url = REST_URL_PREFIX + "/dog/getDogByNum/" + dogNum; return restTemplate.getForObject(url, Object.class); } public Object errorGetDogByNum(Long dogNum){ System.out.println("进入熔断,dogNum是:===>"+dogNum); return "熔断——服务器忙,请稍后再试!"; }
2.3.2.2 看效果
-
将服务提供者停掉,如下:
2.3.3 模拟服务熔断1(服务端模拟异常)
2.3.3.1 修改服务提供者
-
修改代码如下
System.out.println("进入服务提供者,模拟异常...."); System.out.println(2 / dogNum);
-
启动服务,为了方便直接启动本地了
2.3.3.2 看效果
2.3.3.2.1 注释掉熔断处理
- 头疼的500,还不知道啥原因导致的,还得去服务提供端开日志,如下:
2.3.3.2.2 加上熔断处理
- 服务提供者日志,如下:
- 服务消费者
- 页面,如果你是客户,看起来舒服多了
2.4 模糊使用(熔断?降级?)
- 模糊概念:服务熔断是服务降级的强化版。所以如果错误发生在消费端本端也可以使用服务熔断,效果跟上面差不多。
- 下面是错误在消费端的服务熔断,如下:
2.4.1 代码(服务端代码正常,消费端代码模拟异常)
-
如下:
@RequestMapping("/consumer/dog/getDogByNum/{dogNum}") @HystrixCommand(fallbackMethod = "errorGetDogByNum",commandProperties = { //是否开启熔断机制 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ENABLED,value = "true"), //一个时间窗内,发生远程访问错误的次数阈值,达到开启熔断 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,value = "3"), //一个时间窗内,发生远程访问错误的百分比,达到则开启熔断 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,value = "20"), //开启熔断后,多少毫秒内,不发起远程服务访问 @HystrixProperty(name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,value = "3000") }) public Object getDogByNum(@PathVariable("dogNum") Long dogNum){ System.out.println("本地测试熔断...."); System.out.println(2 / dogNum); String url = REST_URL_PREFIX + "/dog/getDogByNum/" + dogNum; return restTemplate.getForObject(url, Object.class); } public Object errorGetDogByNum(Long dogNum){ System.out.println("进入熔断,dogNum是:===>"+dogNum); return "熔断——服务器忙,请稍后再试!"; }
2.4.2 效果
2.4.2.1 未开启熔断时效果
- 如下:
2.4.2.2 开启熔断后效果
-
先测试不会发生错误的dog_num,如下:
-
再测试会发生错误的,如下:
-
再点本应该不会发生错误的,如下:
3. Feign整合Hystrix容错处理
- 为了方便直接在dog-api项目里修改代码,如下两种方式
- 先看原代码
- 先看原代码
3.1 核心代码
3.1.1 实现类和注解
3.1.1.1 方式1
- 添加实现类,如下
- 修改注解,如下
@FeignClient(value = "DOG-PROVIDER",fallback = DogClientApiImpl.class) //方式1
3.1.1.2 方式2
-
添加实现类,如下:
package com.liu.susu.api; import com.liu.susu.pojo.Dog; import feign.hystrix.FallbackFactory; import org.springframework.stereotype.Component; import java.util.List; /** * @Description * @Author susu */ @Component public class DogClientApiFallbackFactory implements FallbackFactory<DogClientApi> { @Override public DogClientApi create(Throwable throwable) { return new DogClientApi() { @Override public String hello() { System.out.println("进入 DogClientApiFallbackFactory 服务降级--->hello"); return "DogClientApiFallbackFactory 服务降级处理,请稍后再试"; } @Override public Object getDogByNum(Long dogNum) { return null; } @Override public List<Dog> getAllDog() { return null; } }; } }
-
修改注解,如下:
@FeignClient(value = "DOG-PROVIDER",fallbackFactory = DogClientApiFallbackFactory.class) //方式2
3.1.2 消费者的yml
- 配置消费者的yml文件(不能忘!!!)
feign: hystrix: enabled: true
3.1.3 看效果
- 启动服务,看效果
- 然后把服务提供者断开,再看效果
好了,就这么多吧,另外一种方式同样的效果,感兴趣的话自己下去试试。