上一节:SpringCloud进击 | 三浅出:服务消费者(Feign)【Finchley版本】
断路器:Hystrix 客户端,字面上理解,就是断开一端和另一端之间的链路。当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个预留的、可处理的备选响应,即服务降级(FallBack),而不是长时间的等待,或者抛出调用方无法处理的异常。
在微服务架构中,根据业务来拆分成一个一个的服务,服务与服务之间可以相互调用(RPC)。在 Spring Cloud 中可以用 RestTemplate+Ribbon 或者 Feign 来调用。但是为了保证它的高可用性,单个服务通常会集群部署。
因为断路/熔断只是作用在服务消费/调用这一端,因此根据上一篇的示例代码,我们只需要改动 wei-consumer-feign 模块,或者 wei-consumer-ribbon 模块相关代码就可以。
三个角色,依旧使用前面章节已经创建好的工程:
Hystrix [hɪst’rɪks] 的中文含义是 “豪猪”,豪猪周身长满了刺,能保护自己不受天敌的伤害,代表了一种防御机制,这与 Hystrix 本身的功能不谋而合,因此 Netflix 团队将该框架命名为 Hystrix,并使用了对应的卡通形象做作为 logo。
在 Ribbon 中使用 Hystrix 断路器,需要改造我们在第二节做好的 wei-consumer-ribbon 应用模块。这里我们将其重命名为 wei-consumer-ribbon-hystrix,端口设置为 8021。
文件 pom.xml 依赖中引入 Hystrix 的起步依赖:spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
当前模块完整依赖如下:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
@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;
}
}
在启动类上添加注解 @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();
}
}
文件 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,接下来就快验证一下吧。
启动。
确认已经正常启动准备工作中的 服务注册中心 和 服务提供者。
启动 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故障节点被剔除,之后不会被调用者访问到。
在 Ribbon 中使用 Hystrix 断路器,需要改造我们在第三节做好的 wei-consumer-feign 应用模块。这里我们将其重命名为 wei-consumer-feign-hystrix,端口设置为 8031。
因为 Feign 中已经依赖了 Hystrix,所以在 pom 依赖上不用做任何改动。但是它没有默认打开,需要在配置文件中设置并开启它。
改造配置文件 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熔断功能
创建一个回调类(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;
}
}
在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,有没有很简单,接下来就快验证一下吧。
启动。
确认已经正常启动准备工作中的 服务注册中心 和 服务提供者。
启动 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故障节点被剔除,之后不会被调用者访问到。
通过使用 Hystrix 断路器,我们能方便的防止雪崩效应,同时使系统具有自动降级和自动恢复服务的效果。
Netflix开源了Hystrix组件,实现了断路器模式/熔断模式,Spring Cloud 对 Hystrix 进行了整合。在微服务架构中,通常有多层服务调用,一个请求需要调用多个服务是非常常见的。如下图:
低级别的服务中的服务故障可能导致用户级联故障(即服务雪崩效应)。当对特定服务的调用达到一定阈值时(Hystrix中的默认值为5秒,20次故障),断路会被打开,返回服务提供的后备方案(Fallback)。
Hystrix回退/容错防止级联故障。如上图,断路打开后,服务返回备选方案(Fallback),这样可以有效避免级联故障。Fallback 回调方法可以直接返回一个预定值。
服务雪崩效应是一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用逐渐放大的过程,最终导致服务级联故障。如果下图所示:
上图中,A 为服务提供者,B 为 A 的服务调用者,C、D、E 是 B 的服务调用者。当 A 的不可用时,引起 B 的不可用,并将不可用逐渐放大到 C、D、E 时,就造成了一种崩塌现象,也就是服务雪崩的形成。
如果把造成服务雪崩的角色分类,则可以划分为 服务提供者、服务消费者。而服务雪崩产生的过程则可以用以下三个阶段来分析形成的原因:
服务雪崩的每个阶段都可能由不同的原因造成,但可以总结一下:
a) 造成 服务提供者不可用 的原因则可能为:
b) 造成 重试加大流量 的原因则可能为:
c) 造成 服务调用者不可用 的原因则可能为:
针对造成服务雪崩的不同原因,可以使用不同的应对策略:
a) 流量控制 的具体措施包括
b) 改进缓存模式 的措施包括
c) 服务自动扩容 的措施主要有
d) 服务调用者降级服务 的措施包括
到此,在 Ribbon 或者 Feign 中使用断路器与容错方案的开发全部完成。
下一节,请继续关注:SpringCloud进击 | 五浅出:服务网关 - 路由(Zuul Router)【Finchley版本】
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