⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Hystrix/ 「芋道源码」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labslab-57 目录。

原创不易,给点个 Star 嘿,一起冲鸭!

1. 概述

在开始 Hystrix 的学习之前,我们先来一起瞅瞅“服务雪崩”、“服务容错”等的概念,方便我们理解为什么要使用 Hystrix 框架。

友情提示:本文是《芋道 Spring Cloud Netflix 服务容错 Hystrix 入门》的弟弟篇。所以内容上,会有蛮多重叠的地方。

写本文的主要目的是,分享在纯 Spring Boot 环境下,如何使用 Hystrix 框架。基本所有的网上文章,都是通过 Spring Cloud Netflix Hystrix 进行 Hystrix 的使用,这样就引入了大量 Spring Cloud 的依赖。而我们的希望,可能仅仅只想使用 https://github.com/Netflix/Hystrix 核心库。

不过绝大多数情况下,我们并不会在纯 Spring Boot 环境下,使用 Hystrix 实现服务容错。

1.1 服务雪崩

在微服务的架构体系中,我们会将系统拆分成多个服务小单元,通过 HTTP 或者 RPC 进行远程调用。如下图所示:

服务雪崩 - 正常

在绝大多数情况下,服务消费者都能正常的远程调用服务提供者。但是某一时刻,服务提供者执行逻辑较慢,又或者网络出现抖动的情况,导致服务消费调用服务提供者超时或者失败。如下图所示:

服务雪崩 - 调用失败

如果这个情况持续一段时间,服务提供者的响应一直很慢,导致服务消费者的响应也跟着很慢,最终引起服务消费者的请求任务积压,也跟着一起出问题了。如下图所示:

友情提示:以 SpringMVC 提供 API 接口举例子,因为 Tomcat 的线程池是有限的,如果个别请求处理很慢,会逐步占用到整个线程池,导致后续其它请求无法被处理。

服务雪崩 - 服务雪崩

这种因为一个下游服务的故障,导致上游服务一起跟着故障的现象,我们称为“服务雪崩”。

1.2 服务容错

针对“服务雪崩”的情况,我们需要进行“服务容错”处理。解决的方向很“简单”,尽量不要去调用故障的服务,避免被拖垮。一般常用的手段有,主要是限流开关

① 限流

通过限制调用服务的频率,避免频繁调用故障服务,导致请求任务积压而自身雪崩。

② 开关

通过关闭对故障服务的调用,停止调用故障服务,从而避免服务雪崩。当然,关闭的前提是,不调用故障服务的情况下,业务逻辑依然可以走下去,或者业务数据的完整性不会被破坏。

一般来说,开关会分成手动开关和自动开关。手动开关比较好了解,自动开关是满足指定条件自动进行关闭。

自动开关比较经典的就是“断路器模式”,它源于 Martin Fowler 大佬在 《CircuitBreaker》 文章的分享。

“断路器”,又称自动开关,它是一种既有手动开关作用,又能自动进行失压、欠压、过载、和短路保护的电器

它可用来分配电能,不频繁地启动异步电动机,对电源线路及电动机等实行保护,**当它们发生严重的过载或者短路及欠压等故障时能自动切断电路,其功能相当于熔断器式开关与过欠热继电器等的组合。**而且在分断故障电流后一般不需要变更零部件,一获得了广泛的应用。

在微服务架构中,“断路器模式”的用途也是类似的。当某个服务提供者发生故障(相当于电器发生短路的情况)时,断路器一旦监控到这个情况,会将开关进行自动关闭。之后,在服务消费者调用该故障服务提供者时,直接抛出错误异常,不进行调用,从而避免调用服务的漫长等待。

友情提示:如果这么描述比较晦涩,稍后我们以 Hystrix 提供的断路器功能,结合它的状态来具体来瞅瞅哈。

1.3 Hystrix

Hystrix 是 Netflix 开源的分布式系统的延迟和容错库。

Hystrix 供分布式系统使用,提供延迟和容错功能,隔离远程系统、访问和第三方程序库的访问点,防止级联失败,保证复杂的分布系统在面临不可避免的失败时,仍能有其弹性。

Hystrix 图标

Netflix 称,在分布式环境中,不可避免会造成一些服务的失败。Hystrix 库旨在控制分布式服务中提供更大容限和服务失败之间的相互关系。Hystrix 通过隔离访问远程系统、服务和第三方库的点,阻止级联故障,从而使复杂的分布式系统更具弹性。

Hystrix 源于 Netflix API 团队在去年启动的弹性工程项目,在此期间,Hystrix 得到了不断发展,并逐渐成熟。现在,在 Netflix 网站中,每天有数十亿的独立线程和信号通过 Hystrix 进行调用,Hystrix 的运行时间和弹性也得到了显著的改善。

Hystrix 比较重要的是三个特性:1)Fallback 服务降级;2)断路器机制;3)资源隔离。

1.3.1 Fallback 服务降级

在服务调用失败时,我们可以通过 Hystrix 实现 Fallback 服务降级。

例如说,对于查询操作,我们给它实现一个 fallback 方法。当请求服务提供者发生异常时,我们可以执行 fallback 方法获得返回结果。示例代码如下:

fallback 示例

一般情况下,fallback 方法的返回结果使用设置的默认值,又或者来自缓存。

友情提示:如果想要深入了解,可阅读《Hystrix 源码解析 —— 请求执行(四)之失败回退逻辑》

1.3.2 断路器机制

Hystrix 内置断路器 HystrixCircuitBreaker 实现,一共有三种状态:

  • CLOSED :关闭
  • OPEN :打开
  • HALF_OPEN :半开

牛逼!HystrixCircuitBreaker 比较神来之笔,就是增加了 HALF_OPEN 状态,不仅仅实现了自动化的关闭,还实现了自动化的打开

其中,断路器处于 OPEN 状态时,链路处于非健康状态,命令执行时,直接调用回退逻辑,跳过正常逻辑。

HystrixCircuitBreaker 状态变迁如下图 :

红线 :初始时,断路器处于 CLOSED 状态,链路处于健康状态。当满足如下条件,断路器从 CLOSED 变成 OPEN 状态:

  • 周期( 可配,HystrixCommandProperties.default_metricsRollingStatisticalWindow = 10000 ms )内,总请求数超过一定( 可配,HystrixCommandProperties.circuitBreakerRequestVolumeThreshold = 20 ) 。
  • 错误请求占总请求数超过一定比例( 可配,HystrixCommandProperties.circuitBreakerErrorThresholdPercentage = 50% ) 。

绿线 :断路器处于 OPEN 状态,命令执行时,若当前时间超过断路器开启时间一定时间( HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds = 5000 ms ),断路器变成 HALF_OPEN 状态,尝试调用正常逻辑,根据执行是否成功,打开或关闭熔断器【蓝线】。

友情提示:如果想要深入了解,可阅读《Hystrix 源码解析 —— 断路器 HystrixCircuitBreaker》

1.3.3 资源隔离

Hystrix 使用了“舱壁隔离模式”来隔离和限制各个请求,从而实现资源的隔离。

Hystrix 通过线程池信号量(Semaphore 两种模式来实现隔离。

① 线程池模式

默认情况下,Hystrix 采用线程池模式来实现隔离。

针对调用的每一个服务,我们给其单独分配一个线程池。例如说,产品服务的调用分配在 A 线程池,用户服务的调用分配在 B 线程池。这样隔离后,两个服务的调用不会相互影响。

② 信号量模式

使用线程池模式来隔离时,需要进行上下文的切换,带来一定的性能损耗。因此,如果对性能有较高要求,且能够接受信号量模式不支持超时的情况,可以考虑采用信号量模式。

友情提示:如果想要深入了解,可阅读《Hystrix 源码解析 —— 命令执行(二)之执行隔离策略》


下面,我们正式开始本文的旅程,开始学习 Hystrix 咯。

2. 快速入门

示例代码对应仓库:

本小节,我们来搭建一个 Hystrix 组件的快速入门示例。步骤如下:

  • 首先,搭建一个 user-service 用户服务,提供获取用户信息的 HTTP API 接口。
  • 然后,搭建一个用户服务的消费者,使用 Hystrix 实现服务容错。

2.1 搭建用户服务

创建 labx-23-user-service 项目,搭建用户服务。代码如下图所示:

项目代码

比较简单,主要是提供 http://127.0.0.1:18080/user/get 接口,获取用户详情。具体的代码,肯定不用艿艿啰嗦讲解哈,点击 labx-23-user-service 查看。

2.2 搭建 Hystrix 示例项目

创建 lab-57-hystrix-demo01 项目,搭建一个用户服务的消费者,使用 Hystrix 实现服务容错。代码如下图所示:

项目代码

2.2.1 引入依赖

pom.xml 文件中,引入 Hystrix 相关依赖。

<?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">
<parent>
<artifactId>lab-57</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-57-hystrix-demo01</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 引入 Hystrix 依赖 -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.18</version>
</dependency>

</dependencies>

</project>

① 引入 hystrix-core 依赖,提供 Hystrix 核心实现。

② 引入 hystrix-javanica 依赖,提供 @HystrixCommand 等注解,搭配 AOP 切面功能,简化我们使用 Hystrix 实现服务容错的功能。

2.2.2 HystrixConfig

创建 HystrixConfig 配置类,创建 HystrixCommandAspect Bean。代码如下:

@Configuration
@EnableAspectJAutoProxy // 开启 AOP 代理的支持
public class HystrixConfig {

@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}

}

通过 HystrixCommandAspect 可以进行扫描 @@HystrixCommand 等注解的切面,使用 Hystrix 进行服务容错。核心代码如下图:

HystrixCommandAspect 源码

2.2.3 DemoController

创建 DemoController 类,提供调用用户服务的 HTTP API 接口。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private RestTemplate restTemplate;

@GetMapping("/get_user")
@HystrixCommand(fallbackMethod = "getUserFallback")
public String getUser(@RequestParam("id") Integer id) {
logger.info("[getUser][准备调用 user-service 获取用户({})详情]", id);
return restTemplate.getForEntity("http://127.0.0.1:18080/user/get?id=" + id, String.class).getBody();
}

public String getUserFallback(Integer id, Throwable throwable) {
logger.info("[getUserFallback][id({}) exception({})]", id, ExceptionUtils.getRootCauseMessage(throwable));
return "mock:User:" + id;
}

}

① 在 #getUser(Integer id) 方法,我们使用 RestTemplate 调用用户服务提供的 /user/get 接口,获取用户详情。

② 在 #getUser(Integer id) 方法,添加了 Hystrix 提供的 @HystrixCommand 注解,设置执行发生 Exception 异常时,执行 fallbackMethod 属性对应的 #getUserFallback(Integer id, Throwable throwable) 方法。注意,fallbackMethod 方法的参数要和原始方法一致,最后一个为 Throwable 异常

通过不同的 Throwable 异常,我们可以进行不同的 fallback 降级处理。极端情况下,Hystrix 断路器熔断(打开)时,不会执行 #getUser(Integer id) 方法,而是直接抛出 Hystrix circuit short-circuited and is OPEN 异常,然后也是进入 fallback 降级处理。

③ 我们来完整看看 @HystrixCommand 注解的参数:

1. fallbackMethod 属性:

指定 fallback 服务降级的处理方法,处理相应的异常。

2. ignoreExceptions 属性:

指定忽略指定的异常 Class,不进行 fallback 服务降级。

3. commandKey 属性:

Hystrix Command 命令,默认未配置情况下,使用方法名。例如说,我们上面的 #getUser(...) 对应的 commandKey 属性默认为 getUser

4. groupKey 属性:

Hystrix Command 命令分组键,用于 Hystrix 根据不同的分组来统计命令的统计、告警、仪表盘信息。

默认未配置情况下,使用方法所在类名。例如说,我们上面的 #getUser(...) 方法所在类为 DemoController,则对应的 groupKey 属性默认为 DemoController。

5. threadPoolKey 属性

线程池名,用于划分不同的线程池,进行资源隔离。

默认未配置情况下,相同 groupKey 的 Hystrix Command 使用同一个线程池。在配置情况下,相同 groupKey + threadPoolKey 使用同一个线程池。也就是说,groupKey必选基础维度,而 threadPoolKey可选的进一步细化维度。

2.2.4 DemoApplication

创建 DemoApplication 类,作为 Hystrix 示例项目的启动类。代码如下:

@SpringBootApplication
public class DemoApplication {

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

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

}

2.3 简单测试

执行 UserServiceApplication 启动用户服务,执行 DemoApplication 启动 Hystrix 示例项目。

① 使用浏览器,访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,成功调用用户服务,返回结果为 User:1

② 停止 UserServiceApplication 关闭用户服务。

使用浏览器,访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,失败调用用户服务,返回结果为 mock:User:1

此时我们会看到如下日志,可以判断触发 Hystrix 的 fallback 服务降级的方法。

2020-05-09 22:32:07.729  INFO 68093 --- [emoController-6] c.i.s.l.h.controller.DemoController      : [getUser][准备调用 user-service 获取用户(1)详情]
2020-05-09 22:32:07.731 INFO 68093 --- [emoController-6] c.i.s.l.h.controller.DemoController : [getUserFallback][id(1) exception(ConnectException: Connection refused (Connection refused))]

疯狂使用浏览器,访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,会触发 Hystrix 断路器熔断(打开),不再执行 #getUser(Integer id) 方法,而是直接 fallback 触发 #getUserFallback(Integer id, Throwable throwable) 方法。日志内容如下:

2020-05-09 22:34:20.946  INFO 68093 --- [nio-8080-exec-2] c.i.s.l.h.controller.DemoController      : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]
2020-05-09 22:34:21.099 INFO 68093 --- [nio-8080-exec-3] c.i.s.l.h.controller.DemoController : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]
2020-05-09 22:34:21.173 INFO 68093 --- [nio-8080-exec-4] c.i.s.l.h.controller.DemoController : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]

④ 重新执行 UserServiceApplication 启动用户服务。

使用浏览器,多次访问 http://127.0.0.1:8080/demo/get_user?id=1 地址,Hystrix 断路器的状态逐步从打开 => 半开 => 关闭。日志内容如下:

// 打开
2020-05-09 22:39:05.226 INFO 68093 --- [io-8080-exec-10] c.i.s.l.h.controller.DemoController : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]
...

// 半开
[getUser][准备调用 user-service 获取用户(1)详情]

// 关闭
[getUser][准备调用 user-service 获取用户(1)详情]
[getUser][准备调用 user-service 获取用户(1)详情]
[getUser][准备调用 user-service 获取用户(1)详情]
...

3. 请求缓存

《芋道 Spring Cloud Netflix 服务容错 Hystrix 入门》「3. 请求缓存」使用方式一模一样,胖友点击查看即可。

4. 请求合并

《芋道 Spring Cloud Netflix 服务容错 Hystrix 入门》「4. 请求合并」使用方式一模一样,胖友点击查看即可。

5. Hystrix Dashboard 监控

hystrix-dashboard 已经废弃,官方描述如下:

This project previously was a part of the Netflix/Hystrix project. It is now deprecated and no longer supported. See the below security section for necessary security considerations.

Spring Cloud Netflix 单独维护了 spring-cloud-netflix-hystrix-dashboard 项目,作为在 Spring Cloud 架构下的 Hystrix Dashboard 监控仪表盘。

因此,如果胖友真的要使用的话,可能只能暂时阅读《芋道 Spring Cloud Netflix 服务容错 Hystrix 入门》「5. Hystrix Dashboard 监控」「6. Turbine 聚合监控」小节。

6. 集成到 Dubbo

示例代码对应仓库:

本小节我们来进行 Dubbo 和 Hystrix 的整合,目前暂时没有框架或者库提供它们的整合,所以我们只能像「2. 快速入门」小节,使用 @HystrixCommand 注解添加在进行 Dubbo RPC 调用的方法上,如下图所示:

Dubbo 示例 +  注解

Apache Dubbo™ 是一款高性能Java RPC框架。

下面,我们来搭建 Dubbo 和 Hystrix 整合的示例,最终项目如下图所示:

项目结构

6.1 用户服务 API

创建 lab-57-hystrix-dubbo-demo-user-service-api 项目,作为用户服务 API。如下图所示:

项目结构

6.1.1 引入依赖

创建 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">
<parent>
<artifactId>lab-57-hystrix-dubbo-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-57-hystrix-dubbo-demo-user-service-api</artifactId>

</project>

6.1.2 UserService

创建 UserService 接口,提供用户服务 API 接口。代码如下:

public interface UserService {

String getUser(Integer id);

}

6.2 用户服务 Provider

创建 lab-57-hystrix-dubbo-demo-user-service 项目,作为用户服务 Provider 实现。如下图所示:

项目结构

友情提示:对 Dubbo 不了解的胖友可以看看《芋道 Spring Boot Dubbo 入门》文章。

考虑到 Nacos 作为注册中心越来越流行,所以这里就使用 Nacos 啦,不了解的胖友可以看看《芋道 Spring Boot 注册中心 Nacos 入门》文章。

6.2.1 引入依赖

创建 pom.xml 文件,引入 Dubbo、Nacos 依赖。

<?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">
<parent>
<artifactId>lab-57-hystrix-dubbo-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-57-hystrix-dubbo-demo-user-service</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入用户服务 API 包 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>lab-57-hystrix-dubbo-demo-user-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- 引入 Spring Boot 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<!-- 实现对 Dubbo 的自动化配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>

<!-- 使用 Nacos 作为注册中心 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.4.1</version>
</dependency>
</dependencies>

</project>

6.2.2 配置文件

创建 application.yaml 配置文件,添加 Dubbo 配置项,并使用 Nacos 作为注册中心。

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
# Dubbo 应用配置
application:
name: user-service # 应用名
# Dubbo 注册中心配
registry:
address: nacos://127.0.0.1:8848 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
# Dubbo 服务提供者协议配置
protocol:
port: -1 # 协议端口。使用 -1 表示随机端口。
name: dubbo # 使用 `dubbo://` 协议。更多协议,可见 http://dubbo.apache.org/zh-cn/docs/user/references/protocol/introduction.html 文档
# Dubbo 服务提供者配置
provider:
timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
UserRpcService:
version: 1.0.0
# 配置扫描 Dubbo 自定义的 @Service 注解,暴露成 Dubbo 服务提供者
scan:
base-packages: cn.iocoder.springboot.lab57.userservice.service

dubbo 配置项,添加 Dubbo 相关的配置项。

dubbo.registry.address 配置项,设置使用 Nacos 作为配置中心。

6.2.3 UserServiceImpl

创建 UserServiceImpl 类,实现 UserService 接口,提供 Dubbo 用户服务。代码如下:

@org.apache.dubbo.config.annotation.Service(protocol = "dubbo", version = "1.0.0")
public class UserServiceImpl implements UserService {

@Override
public String getUser(Integer id) {
return "User:" + id;
}

}

6.2.4 UserServiceApplication

创建 UserServiceApplication 类,用户服务的启动类。代码如下:

@SpringBootApplication
public class UserServiceApplication {

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

}

6.3 搭建 Hystrix 示例项目

创建 lab-57-hystrix-dubbo-demo-application 项目,搭建一个用户服务的消费者,使用 Dubbo 进行调用,使用 Hystrix 实现服务容错。如下图所示:

项目结构

6.3.1 引入依赖

创建 pom.xml 文件,引入 Hystrix、Dubbo、Nacos 依赖。

<?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">
<parent>
<artifactId>lab-57-hystrix-dubbo-demo</artifactId>
<groupId>cn.iocoder.springboot.labs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>lab-57-hystrix-dubbo-demo-application</artifactId>

<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<spring.boot.version>2.2.4.RELEASE</spring.boot.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- 引入用户服务 API 包 -->
<dependency>
<groupId>cn.iocoder.springboot.labs</groupId>
<artifactId>lab-57-hystrix-dubbo-demo-user-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- 引入 SpringMVC 相关依赖,并实现对其的自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 实现对 Dubbo 的自动化配置 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.4.1</version>
</dependency>

<!-- 使用 Nacos 作为注册中心 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.4.1</version>
</dependency>

<!-- 引入 Hystrix 依赖 -->
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.18</version>
</dependency>
</dependencies>

</project>

相比「6.2.1 引入依赖」小节来说,多引入 Hystrix 相关依赖进行服务容错。

6.3.2 配置文件

创建 application.yaml 配置文件,添加 Dubbo 和 Nacos 配置项。

# dubbo 配置项,对应 DubboConfigurationProperties 配置类
dubbo:
# Dubbo 应用配置
application:
name: demo-consumer # 应用名
# Dubbo 注册中心配置
registry:
address: nacos://127.0.0.1:8848 # 注册中心地址。个鞥多注册中心,可见 http://dubbo.apache.org/zh-cn/docs/user/references/registry/introduction.html 文档。
# Dubbo 消费者配置
consumer:
timeout: 1000 # 【重要】远程服务调用超时时间,单位:毫秒。默认为 1000 毫秒,胖友可以根据自己业务修改
UserRpcService:
version: 1.0.0

「6.2.2 配置文件」小节差不多,不重复啰嗦哈。

6.3.3 HystrixConfig

HystrixConfig

创建 HystrixConfig Bean。代码如下:

@Configuration
@EnableAspectJAutoProxy // 开启 AOP 代理的支持
public class HystrixConfig {

@Bean
public HystrixCommandAspect hystrixCommandAspect() {
return new HystrixCommandAspect();
}

}

「2.2.2 HystrixConfig」小节一样。

6.3.4 DemoController

创建 DemoController 类,提供一个通过 Dubbo 调用用户服务提供者的 HTTP 接口。代码如下:

@RestController
@RequestMapping("/demo")
public class DemoController {

private Logger logger = LoggerFactory.getLogger(getClass());

@Reference(protocol = "dubbo", version = "1.0.0")
private UserService userService;

@GetMapping("/get_user")
@HystrixCommand(fallbackMethod = "getUserFallback")
public String getUser(@RequestParam("id") Integer id) {
logger.info("[getUser][准备调用 user-service 获取用户({})详情]", id);
return userService.getUser(id);
}

public String getUserFallback(Integer id, Throwable throwable) {
logger.info("[getUserFallback][id({}) exception({})]", id, ExceptionUtils.getRootCauseMessage(throwable));
return "mock:User:" + id;
}

}

#getUser(Integer id) 方法上,添加了 @HystrixCommand 注解,从而在 Dubbo 调用失败时,执行 #getUserFallback(Integer id, Throwable throwable) 方法进行服务容错。

6.3.5 DemoApplication

创建 DemoApplication 类,创建应用启动类。代码如下:

@SpringBootApplication
public class DemoApplication {

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

}

6.4 简单测试

执行 UserServiceApplication 启动用户服务,执行 DemoApplication 启动 Hystrix 示例项目。

① 使用浏览器,访问 http://127.0.0.1:8080/feign-demo/get_user?id=1 地址,成功调用用户服务,返回结果为 User:1

② 停止 UserServiceApplication 关闭用户服务。

使用浏览器,访问 http://127.0.0.1:8080/feign-demo/get_user?id=1 地址,失败调用用户服务,返回结果为 mock:User:1

此时我们会看到如下日志,可以判断触发 Hystrix 的 fallback 服务降级的方法。

2020-05-15 07:18:06.183  INFO 75980 --- [emoController-3] c.i.s.l.demo.controller.DemoController   : [getUser][准备调用 user-service 获取用户(1)详情]
2020-05-15 07:18:06.184 INFO 75980 --- [emoController-3] c.i.s.l.demo.controller.DemoController : [getUserFallback][id(1) exception(RemotingException: message can not send, because channel is closed . url:dubbo://192.168.43.240:20880/com.alibaba.cloud.dubbo.service.DubboMetadataService?anyhost=true&application=demo-consumer&bind.ip=192.168.43.240&bind.port=20880&check=false&codec=dubbo&deprecated=false&dubbo=2.0.2&dynamic=true&generic=true&group=user-service&heartbeat=60000&interface=com.alibaba.cloud.dubbo.service.DubboMetadataService&lazy=false&methods=getAllServiceKeys,getServiceRestMetadata,getExportedURLs,getAllExportedURLs&pid=75980&qos.enable=false&register.ip=192.168.43.240&release=2.7.4.1&remote.application=user-service&revision=2.2.0.RELEASE&side=consumer&sticky=false&timestamp=1589498209533&version=1.0.0)]

疯狂使用浏览器,访问 http://127.0.0.1:8080/feign-demo/get_user?id=1 地址,会触发 Hystrix 断路器熔断(打开),不再执行 #getUser(Integer id) 方法,而是直接 fallback 触发 #getUserFallback(Integer id, Throwable throwable) 方法。日志内容如下:

2020-05-15 07:20:02.809  INFO 75980 --- [nio-8080-exec-1] c.i.s.l.demo.controller.DemoController   : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]
2020-05-15 07:20:02.907 INFO 75980 --- [nio-8080-exec-2] c.i.s.l.demo.controller.DemoController : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]
2020-05-15 07:20:02.995 INFO 75980 --- [nio-8080-exec-3] c.i.s.l.demo.controller.DemoController : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]

④ 重新执行 UserServiceApplication 启动用户服务。

使用浏览器,多次访问 http://127.0.0.1:8080/feign-demo/get_user?id=1 地址,Hystrix 断路器的状态逐步从打开 => 半开 => 关闭。日志内容如下:

// 打开
2020-05-14 21:27:28.560 INFO 71995 --- [nio-8080-exec-5] .f.UserServiceFeignClientFallbackFactory : [getUserFallback][id(1) exception(RuntimeException: Hystrix circuit short-circuited and is OPEN)]
...

// 半开
2020-05-15 07:21:43.486 INFO 75980 --- [moController-10] c.i.s.l.demo.controller.DemoController : [getUser][准备调用 user-service 获取用户(1)详情]

// 关闭
2020-05-15 07:21:50.449 INFO 75980 --- [moController-10] c.i.s.l.demo.controller.DemoController : [getUser][准备调用 user-service 获取用户(1)详情]
2020-05-15 07:21:50.653 INFO 75980 --- [moController-10] c.i.s.l.demo.controller.DemoController : [getUser][准备调用 user-service 获取用户(1)详情]
2020-05-15 07:21:50.818 INFO 75980 --- [moController-10] c.i.s.l.demo.controller.DemoController : [getUser][准备调用 user-service 获取用户(1)详情]
...

666. 彩蛋

= = 没有彩蛋~

更多关于 Hystrix 的内容,胖友可以访问《Hystrix 源码解析》

文章目录
  1. 1. 1. 概述
    1. 1.1. 1.1 服务雪崩
    2. 1.2. 1.2 服务容错
    3. 1.3. 1.3 Hystrix
      1. 1.3.1. 1.3.1 Fallback 服务降级
      2. 1.3.2. 1.3.2 断路器机制
      3. 1.3.3. 1.3.3 资源隔离
  2. 2. 2. 快速入门
    1. 2.1. 2.1 搭建用户服务
    2. 2.2. 2.2 搭建 Hystrix 示例项目
      1. 2.2.1. 2.2.1 引入依赖
      2. 2.2.2. 2.2.2 HystrixConfig
      3. 2.2.3. 2.2.3 DemoController
      4. 2.2.4. 2.2.4 DemoApplication
    3. 2.3. 2.3 简单测试
  3. 3. 3. 请求缓存
  4. 4. 4. 请求合并
  5. 5. 5. Hystrix Dashboard 监控
  6. 6. 6. 集成到 Dubbo
    1. 6.1. 6.1 用户服务 API
      1. 6.1.1. 6.1.1 引入依赖
      2. 6.1.2. 6.1.2 UserService
    2. 6.2. 6.2 用户服务 Provider
      1. 6.2.1. 6.2.1 引入依赖
      2. 6.2.2. 6.2.2 配置文件
      3. 6.2.3. 6.2.3 UserServiceImpl
      4. 6.2.4. 6.2.4 UserServiceApplication
    3. 6.3. 6.3 搭建 Hystrix 示例项目
      1. 6.3.1. 6.3.1 引入依赖
      2. 6.3.2. 6.3.2 配置文件
      3. 6.3.3. 6.3.3 HystrixConfig
      4. 6.3.4. 6.3.4 DemoController
      5. 6.3.5. 6.3.5 DemoApplication
    4. 6.4. 6.4 简单测试
  7. 7. 666. 彩蛋