51.Spring Boot 服务容错 Hystrix 入门
51.Spring Boot 服务容错 Hystrix 入门
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 供分布式系统使用,提供延迟和容错功能,隔离远程系统、访问和第三方程序库的访问点,防止级联失败,保证复杂的分布系统在面临不可避免的失败时,仍能有其弹性。
Netflix 称,在分布式环境中,不可避免会造成一些服务的失败。Hystrix 库旨在控制分布式服务中提供更大容限和服务失败之间的相互关系。Hystrix 通过隔离访问远程系统、服务和第三方库的点,阻止级联故障,从而使复杂的分布式系统更具弹性。
Hystrix 源于 Netflix API 团队在去年启动的弹性工程项目,在此期间,Hystrix 得到了不断发展,并逐渐成熟。现在,在 Netflix 网站中,每天有数十亿的独立线程和信号通过 Hystrix 进行调用,Hystrix 的运行时间和弹性也得到了显著的改善。
Hystrix 比较重要的是三个特性:1)Fallback 服务降级;2)断路器机制;3)资源隔离。
1.3.1 Fallback 服务降级
在服务调用失败时,我们可以通过 Hystrix 实现 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. 快速入门
示例代码对应仓库:
- 用户服务:
 labx-23-user-service- Hystrix 示例项目:
 lab-57-hystrix-demo01
本小节,我们来搭建一个 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 进行服务容错。核心代码如下图:

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
示例代码对应仓库:
- 用户服务 API:
 lab-57-hystrix-dubbo-demo-user-service-api- 用户服务 Provider:
 lab-57-hystrix-dubbo-demo-user-service- Hystrix 示例项目:
 lab-57-hystrix-dubbo-demo-application
本小节我们来进行 Dubbo 和 Hystrix 的整合,目前暂时没有框架或者库 提供它们的整合,所以我们只能像「2. 快速入门」小节,使用 @HystrixCommand 注解添加在进行 Dubbo RPC 调用的方法上,如下图所示:

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®ister.ip=192.168.43.240&release=2.7.4.1&remote.application=user-service&revision=2.2.0.RELEASE&side=consumer&sticky=false×tamp=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)详情]
...来源:https://blog.csdn.net/weixin_42073629/article/details/106416135

