1.前言
断路器:Hystrix 客户端,字面上理解,就是断开一端和另一端之间的链路。当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个预留的、可处理的备选响应,即服务降级(FallBack),而不是长时间的等待,或者抛出调用方无法处理的异常。
在微服务架构中,根据业务来拆分成一个一个的服务,服务与服务之间可以相互调用(RPC)。在 Spring Cloud 中可以用 RestTemplate+Ribbon 或者 Feign 来调用。但是为了保证它的高可用性,单个服务通常会集群部署。
上一节:SpringCloud进击 | 三浅出:服务消费者(Feign)【Finchley版本】
2.准备
因为断路/熔断只是作用在服务消费/调用这一端,因此根据上一篇的示例代码,我们只需要改动 wei-consumer-feign 模块,或者 wei-consumer-ribbon 模块相关代码就可以。
三个角色,依旧使用前面章节已经创建好的工程:
- 服务注册中心:wei-eureka-server,端口 8090,启动一个(无需修改,正常启动)
- 服务提供者:wei-service-provider,分别以 8010、8011 端口启动一次,获得两个实例的集群(以端口号正常启动)
- 服务消费者:改造 wei-consumer-ribbon 或者 wei-consumer-feign模块(改造对象)
3.进击
Hystrix [hɪst’rɪks] 的中文含义是 “豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与 Hystrix 本身的功能不谋而合,因此 Netflix 团队将该框架命名为 Hystrix,并使用了对应的卡通形象做作为 logo。
3.1.在Ribbon中使用Hystrix
在 Ribbon 中使用 Hystrix 断路器,需要改造我们在第二节做好的 wei-consumer-ribbon 应用模块。这里我们将其重命名为 wei-consumer-ribbon-hystrix,端口设置为 8021。
3.1.1.改造pom.xml依赖
文件 pom.xml 依赖中引入 Hystrix 的起步依赖:spring-cloud-starter-netflix-hystrix
<!--Hystrix起步依赖,在Ribbon使用断路器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
当前模块完整依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--客户端负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--Hystrix起步依赖,在Ribbon使用断路器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.1.2.改造Service类
@HystrixCommand由名为“javanica”的Netflix contrib库提供。Spring Cloud 使用该注释在连接到Hystrix断路器的代理中自动包装Spring bean。断路器计算何时打开和关闭电路,以及在发生故障时应该做什么。
方法加上 @HystrixCommand 注解,该注解对该方法创建了熔断器的功能,并指定 fallbackMethod 熔断方法,当服务不可用时会执行快速失败,直接返回预定值,而不是等待响应超时,这很好的控制了容器的线程阻塞。
package com.wei.service.demo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class DemoRibbonService {
@Autowired
private RestTemplate restTemplate;
/**
* 创建一个新接口用来消费服务提供者提供的接口
* 用程序名替代了具体的url地址,在ribbon中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的url替换掉服务名
*
* 方法加上@HystrixCommand注解,该注解对该方法创建了熔断器的功能,并指定fallbackMethod熔断方法(在Ribbon中使用熔断器)
*/
@HystrixCommand(fallbackMethod = "returnError")
public String getDemoRibbonServiceName(String name) {
String result = restTemplate.getForObject("http://wei-service-provider/demo/info?name=" + name, String.class);
result += "[Ribbon + REST]";
System.out.println(result);
return result;
}
/**
* 当服务不可用时会执行快速失败,直接返回一组字符串,而不是等待响应超时,这很好的控制了容器的线程阻塞
*
* @return 自定义字符串
*/
private String returnError() {
String result = "[Ribbon] 我是自定义值:我是Hystrix熔断器的回调方法返回值,我的出现代表服务已出现故障";
System.out.println(result);
return result;
}
}
3.1.3.改造启动类
在启动类上添加注解 @EnableHystrix,开启 Hystrix 熔断功能。
package com.wei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* 注解@EnableHystrix,开启Hystrix熔断功能
*/
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class WeiConsumerRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(WeiConsumerRibbonApplication.class, args);
}
/**
* 注解@Bean,向程序注入一个Bean
* 注解@LoadBalanced,开启RestTemplate的负载均衡功能
* 初始化RestTemplate,用来发起 REST 请求
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.1.4.确认配置文件
文件 application.yml 除了修改端口号为8021外,无需其它修改。
server:
port: 8021
spring:
application:
name: wei-consumer-ribbon-hystrix # 指定进行服务注册时该服务的名称,服务与服务之间相互调用一般都是根据这个name
eureka:
client:
service-url:
defaultZone: http://localhost:8090/eureka/ # 指定进行服务注册的地址
好了,到此,在 Ribbon 中使用 Hystrix 功能的实现已全部改造OK,接下来就快验证一下吧。
启动。
3.1.5.测试
确认已经正常启动准备工作中的 服务注册中心 和 服务提供者。
启动 wei-consumer-ribbon-hystrix 应用模块的启动类,然后反复请求URL:http://localhost:8021/demo/info?name=tester
会看到浏览器依旧还是像之前一样,在8010和8011服务之间打印输出:
Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8010[Ribbon + REST]
Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8011[Ribbon + REST]
但是,如果关掉一个实例呢?
停掉8011端口的实例(这里可以假想服务提供者8011是因为种种原因而导致它的不可用),再访问URL:http://localhost:8021/demo/info?name=tester
[Ribbon] Sorry, tester,我是Hystrix熔断器的回调方法返回值,我的出现代表服务已出现故障
Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8010[Ribbon + REST]
有木有!当请求通过负载均衡被分发到服务提供者8011节点上的时候,它返回了通过 Hystrix 定义的 fallbackMethod 容错方案,执行快速了失败。而再次请求时,请求被分发到了正常的8010节点上。且一段时间之后,8011故障节点被剔除,之后不会被调用者访问到。
3.2.在Feign中使用Hystrix
在 Ribbon 中使用 Hystrix 断路器,需要改造我们在第三节做好的 wei-consumer-feign 应用模块。这里我们将其重命名为 wei-consumer-feign-hystrix,端口设置为 8031。
因为 Feign 中已经依赖了 Hystrix,所以在 pom 依赖上不用做任何改动。但是它没有默认打开,需要在配置文件中设置并开启它。
3.2.1.开启Hystrix
改造配置文件 application.yml,添加配置:feign.hystrix.enabled=true。Spring Cloud Finchley.SR1 版本的配置文件 application.yml 提示找不到 feign 属性,但我们可以不用理会,项目跑起来一样有效果。
feign:
hystrix:
enabled: true
当前模块完整配置如下:
server:
port: 8031
spring:
application:
name: wei-consumer-feign-hystrix # 指定进行服务注册时该服务的名称,服务与服务之间相互调用一般都是根据这个name
eureka:
client:
service-url:
defaultZone: http://localhost:8090/eureka/ # 指定进行服务注册的地址
feign:
hystrix:
enabled: true # 开启Hystrix熔断功能
3.2.2.创建回调类
创建一个回调类(DemoFallback)作为容错方案,实现并重载 IDemoFeignService 接口中的服务消费方法。
package com.wei.service.hystrix;
import com.wei.service.demo.IDemoFeignService;
import org.springframework.stereotype.Component;
@Component
public class DemoFallback implements IDemoFeignService {
/**
* 通过@FeignClient("Eureka服务名称"),来指定调用消费哪个服务
* 注解@RequestMapping映射微服务中的URL
*
* @return 案例
*/
@Override
public String getDemoFeignServiceName(String name) {
String result = "[Feign] Sorry, " + name +",我是熔断器的回调方法返回值,我的出现代表服务已出现故障";
System.out.println(result);
return result;
}
}
3.2.3.改造Service
在Service类中添加 fallback 属性,指定 fallback 类,以在服务熔断的时候返回 fallback 类中的内容。
package com.wei.service.demo;
import com.wei.service.hystrix.DemoFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 通过@FeignClient("Eureka服务名称"),来指定调用消费哪个服务
* fallback:指定 fallback 类,以在服务熔断的时候返回 fallback 类中的内容
*/
@FeignClient(value = "wei-service-provider", fallback = DemoFallback.class)
public interface IDemoFeignService {
/**
* 注解@RequestMapping,映射服务提供者中的URL
* @param name 入参
* @return
*/
@RequestMapping(value = "/demo/info", method = RequestMethod.GET)
String getDemoFeignServiceName(@RequestParam(value = "name") String name);
// Feign客户端和Ribbon类似,同样实现了客户端的负载均衡
// 与Ribbon不同的是,Feign的调用与本地接口的调用更加类似,并且更加便捷、更加优雅,传入参数较多时得以体现
}
好了,到此,在 Feign 中使用 Hystrix 功能的实现已全部改造OK,有没有很简单,接下来就快验证一下吧。
启动。
3.2.4.测试
确认已经正常启动准备工作中的 服务注册中心 和 服务提供者。
启动 wei-consumer-feign-hystrix 应用模块的启动类,然后反复请求URL:http://localhost:8031/demo/info?name=tester
会看到浏览器依旧还是像之前一样,在8010和8011服务之间打印输出:
Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8010[Feign]
Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8011[Feign]
同样,如果关掉一个实例呢?
停掉8010端口的实例(这里可以假想服务提供者8010节点是因为种种原因而导致它的不可用),再访问URL:http://localhost:8031/demo/info?name=tester
[Feign] Sorry, tester,我是熔断器的回调方法返回值,我的出现代表服务已出现故障[Feign]
Hi,tester,我是服务,我被调用了,服务名为:wei-service-provider,端口为:8011[Feign]
有木有!当请求通过负载均衡被分发到服务提供者8010节点上的时候,它返回了通过 Hystrix 定义的 fallback 容错方案,执行快速了失败。而再次请求时,请求被分发到了正常的8011节点上。且一段时间之后,8010故障节点被剔除,之后不会被调用者访问到。
4.总结
通过使用 Hystrix 断路器,我们能方便的防止雪崩效应,同时使系统具有自动降级和自动恢复服务的效果。
4.1.断路器:Hystrix客户端
Netflix开源了Hystrix组件,实现了断路器模式/熔断模式,Spring Cloud 对 Hystrix 进行了整合。在微服务架构中,通常有多层服务调用,一个请求需要调用多个服务是非常常见的。如下图:
低级别的服务中的服务故障可能导致用户级联故障(即服务雪崩效应)。当对特定服务的调用达到一定阈值时(Hystrix中的默认值为5秒,20次故障),断路会被打开,返回服务提供的后备方案(Fallback)。
Hystrix回退/容错防止级联故障。如上图,断路打开后,服务返回备选方案(Fallback),这样可以有效避免级联故障。Fallback 回调方法可以直接返回一个预定值。
4.2.服务雪崩效应
服务雪崩效应是一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用逐渐放大的过程,最终导致服务级联故障。如果下图所示:
上图中,A 为服务提供者,B 为 A 的服务调用者,C、D、E 是 B 的服务调用者。当 A 的不可用时,引起 B 的不可用,并将不可用逐渐放大到 C、D、E 时,就造成了一种崩塌现象,也就是服务雪崩的形成。
4.3.服务雪崩原因
如果把造成服务雪崩的角色分类,则可以划分为 服务提供者、服务消费者。而服务雪崩产生的过程则可以用以下三个阶段来分析形成的原因:
- 服务提供者不可用
- 重试加大流量
- 服务调用者不可用
服务雪崩的每个阶段都可能由不同的原因造成,但可以总结一下:
a) 造成 服务提供者不可用 的原因则可能为:
- 网络/硬件故障:可能为硬件损坏造成的服务器主机宕机,网络故障造成的服务提供者的不可访问
- 程序BUG:程序BUG会引发异常,处理不当也会造成服务的不可用
- 大量请求:在秒杀和大促销开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用
- 缓存击穿:缓存击穿一般发生在缓存应用重启,所有缓存被清空时,以及短时间内大量缓存失效时。大量的缓存不命中,使请求直击后端,造成服务提供者超负荷运行,引起服务不可用
b) 造成 重试加大流量 的原因则可能为:
- 用户重试:由于服务的不可用,导致用户不断尝试请求,不断刷新页面甚至提交表单
- 代码逻辑重试:服务调用者存在大量服务提供者异常后的重试逻辑
c) 造成 服务调用者不可用 的原因则可能为:
- 资源耗尽:当服务调用者使用同步调用时,会产生大量的等待线程占用系统资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是导致服务雪崩效应产生了
- 内存溢出
4.4.服务雪崩应对
针对造成服务雪崩的不同原因,可以使用不同的应对策略:
- 流量控制
- 改进缓存模式
- 服务自动扩容
- 服务调用者降级服务
a) 流量控制 的具体措施包括
- 网关限流
- 用户交互限流
- 关闭重试
b) 改进缓存模式 的措施包括
- 缓存预加载
- 同步改为异步刷新
c) 服务自动扩容 的措施主要有
- AWS 的 auto scaling
d) 服务调用者降级服务 的措施包括
- 资源隔离:主要是对调用服务的线程池进行隔离
- 对依赖服务进行分类
- 不可用服务的调用快速失败
到此,在 Ribbon 或者 Feign 中使用断路器与容错方案的开发全部完成。
下一节,请继续关注:SpringCloud进击 | 五浅出:服务网关 - 路由(Zuul Router)【Finchley版本】
6.物语
SpringCloud进击 | 一浅出:服务注册与发现(Eureka)【Finchley版本】
SpringCloud进击 | 二浅出:服务消费者(Ribbon+REST)【Finchley版本】
SpringCloud进击 | 三浅出:服务消费者(Feign)【Finchley版本】
SpringCloud进击 | 四浅出:断路器与容错(Hystrix)【Finchley版本】
SpringCloud进击 | 五浅出:服务网关 - 路由(Zuul Router)【Finchley版本】
SpringCloud进击 | 六浅出:服务网关 - 过滤器(Zuul Filter)【Finchley版本】
SpringCloud进击 | 七浅出:配置中心(Git配置与更新)【Finchley版本】
SpringCloud进击 | 一深入:配置中心(服务化与高可用)【Finchley版本】
SpringCloud进击 | 二深入:配置中心(消息总线)【Finchley版本】
SpringCloud进击 | 三深入:服务链路跟踪(Spring Cloud Sleuth)【Finchley版本】
SpringCloud进击 | 四深入:服务链路跟踪(Sleuth+Zipkin+RabbitMQ整合)【Finchley版本】
SpringCloud进击 | 五深入:断路器监控(Hystrix Dashboard)【Finchley版本】
SpringCloud进击 | 六深入:断路器聚合监控(Hystrix Turbine)【Finchley版本】
SpringCloud进击 | 七深入:高可用的服务注册中心【Finchley版本】
参考资料:https://springcloud.cc/spring-cloud-netflix.html
本节源码:https://github.com/itanping/wei-springcloud/tree/master/chapter04-hystrix