前言
前一章节, 我们知道了如何利用 RestTemplate+Ribbon 和 Feign 的方式进行服务的调用. 在微服务架构中, 一个服务可能会调用很多的其他微服务应用, 虽然做了多集群部署, 但可能还会存在诸如网络原因或者服务提供者自身处理的原因, 或多或少都会出现请求失败或者请求延迟问题, 若服务提供者长期未对请求做出回应, 服务消费者又不断的请求下, 可能就会造成服务提供者服务崩溃, 进而服务消费者也一起跟着不可用, 严重的时候就发生了系统雪崩了. 鉴于此, 产生了断路器等一系列的服务保护机制. 本章节, 就来说下如何利用 Hystrix 进行容错处理.
一点知识
容错处理手段
雪崩效应
熔断器
Hystrix 实践
何为 Hystrix
常规方式整合 Hystrix
Feign 整合 Hystrix
参考资料
总结
最后
老生常谈
一点知识
按照此系列的惯例, 我们先来了解下一些相关的知识.
注: 以下部分内容转至大佬纯洁的微笑: 熔断器 Hystrix.
容错处理手段
容错处理是指软件运行时, 能对由非正常因素引起的运行错误给出适当的处理或信息提示, 使软件运行正常结束 -- 百度百科
从百度百科的解释中可以看出, 简单理解, 所谓的容错处理其实就是捕获异常了, 不让异常影响系统的正常运行, 正如 java 中的 try catch 一样.
而在微服务调用中, 自身异常可自行处理外, 对于依赖的服务若发生错误, 或者调用异常, 或者调用时间过长等原因时, 避免长时间等待, 造成系统资源耗尽.
一般上都会通过设置请求的超时时间, 如 http 请求中的 ConnectTimeout 和 ReadTimeout; 再或者就是使用熔断器模式, 隔离问题服务, 防止级联错误等.
雪崩效应
在微服务架构中, 存在很多的微服务单元, 各个微服务之间通过网络进行通讯, 难免出现依赖关系, 若某一个单元出现故障, 就很容易因依赖关系而引发故障的蔓延, 产生 "雪崩效应", 最终导致整个系统的瘫痪.
下面这张图, 相比大家都有看过了.
如图所示: A 作为服务提供者, B 为 A 的服务消费者, C 和 D 是 B 的服务消费者. A 不可用引起了 B 的不可用, 并将不可用像滚雪球一样放大到 C 和 D 时, 雪崩效应就形成了. 也就应了那句话: 星星之火, 可以燎原!
熔断器
熔断器, 和现实生活中的空气开关作用很像. 它可以实现快速失败, 如果它在一段时间内侦测到许多类似的错误, 会强迫其以后的多个调用快速失败, 不再访问远程服务器, 从而防止应用程序不断地尝试执行可能会失败的操作, 使得应用程序继续执行而不用等待修正错误, 或者浪费 CPU 时间去等到长时间的超时产生. 熔断器也可以使应用程序能够诊断错误是否已经修正, 如果已经修正, 应用程序会再次尝试调用操作.
熔断器模式就像是那些容易导致错误的操作的一种代理. 这种代理能够记录最近调用发生错误的次数, 然后决定使用允许操作继续, 或者立即返回错误.
可以看出, 熔断器一共有三种状态, 之间转换关系如下:
关闭状态
当熔断器处于关闭状态时, 请求是可以被放行的;
当熔断器统计的失败次数触发开关时, 转为打开状态.
打开状态
当熔断器处于打开状态时, 所有请求都是不被放行的, 直接返回失败;
只有在经过一个设定的时间窗口周期后, 熔断器才会转换到半开状态
半开状态
当熔断器处于半开状态时, 当前只能有一个请求被放行;
这个被放行的请求获得远端服务的响应后, 假如是成功的, 熔断器转换为关闭状态, 否则转换到打开状态.
个人觉得, 主要还是快速失败, 避免请求堆积, 压垮服务器. 进而起到保护服务高可用的目的.
Hystrix 实践
何为 Hystrix
Hystrix 是一个实现了超时机制和断路器模式的工具类库.
Hystrix 是有 Netflix 开源的一个延迟和容错库, 用于隔离访问远程系统, 服务或第三方库, 防止级联失败, 从而提升系统的可用性和容错性.
Hystrix 容错机制:
包裹请求: 使用 HystrixCommand 包裹对依赖的调用逻辑, 每个命令在独立线程中执行, 这是用到了设计模式 "命令模式".
跳闸机制: 当某服务的错误率超过一定阈值时, Hystrix 可以自动或手动跳闸, 停止请求该服务一段时间.
资源隔离: Hystrix 为每个依赖都维护了一个小型的线程池, 如果该线程池已满, 发往该依赖的请求就被立即拒绝, 而不是排队等候, 从而加速判定失败.
监控: Hystrix 可以近乎实时的监控运行指标和配置的变化. 如成功, 失败, 超时, 被拒绝的请求等.
回退机制: 当请求失败, 超时, 被拒绝, 或当断路器打开时, 执行回退逻辑. 回退逻辑可自定义.
自我修复: 断路器打开一段时间后, 会自动进入半开状态, 断路器打开, 关闭, 半开的逻辑转换.
下图就是 Hystrix 的回退策略, 防止级联故障.
常规方式整合 Hystrix
创建个工程 spring-cloud-hystrix 工程.
0. 引入 POM 依赖.
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
1. 启动类, 加入注解 @EnableHystrix, 同时申明一个实现负载均衡的 RestTemplate.(关于消费者服务可查看: 第四章: 服务消费者(RestTemple+Ribbon+Feign), 这里不再阐述了.)
- /**
- * 熔断器示例
- * @author oKong
- *
- */
- @SpringBootApplication
- @EnableHystrix
- @EnableDiscoveryClient
- @Slf4j
- public class HystrixApplication {
- public static void main(String[] args) throws Exception {
- SpringApplication.run(HystrixApplication.class, args);
- log.info("sprign-cloud-hystrix 启动!");
- }
- @Bean
- @LoadBalanced
- public RestTemplate restTemplat() {
- return new RestTemplate();
- }
- }
2. 编写一个测试类, 加入 @HystrixCommand, 指定 fallbackMethod 方法.
RibbonController.java
- /**
- * ribbon 常规方式 - 示例
- * @author oKong
- *
- */
- @RestController
- @Slf4j
- public class RibbonController {
- @Autowired
- RestTemplate restTemplate;
- @GetMapping("/ribbon")
- @HystrixCommand(fallbackMethod="fallback")
- public String hello(String name) {
- log.info("使用 restTemplate 调用服务, 参数 name:{}", name);
- return restTemplate.getForObject("http://eureka-client/hello?name=" + name, String.class);
- }
- /**
- * 发生熔断时调用的方法
- * @param name
- * @param throwable 发生异常时的异常信息
- * @return
- */
- public String fallback(String name,Throwable throwable) {
- log.error("熔断发生了:{}", throwable);
- log.warn("restTemplate 调用服务发生熔断, 参数 name:{}", name);
- return "restTemplate 调用服务发生熔断, 参数 name:" + name;
- }
- }
注意: 这里 fallback 方法加入了一个参数 throwable, 当发生熔断时, 可以获悉发生熔断的异常信息, 便于定位问题和原因.
3. 启动应用, 访问: http://127.0.0.1:8038/ribbon?name=oKong . 正常情况下, spring-cloud-eureka-client 应用正常运行时, 返回正常结果:
现在我们停止提供者服务, 再次访问, 可以看见已经进入熔断方法了:
控制台可以看见异常输出:
由于实例尚未被剔除注册中心的服务列表, 所以提示是连接超时, 等待一段时间后, 再次访问服务, 可以看见是提示实例不存在了:
注意: 对于 @HystrixCommand 注解, 我们可以放在任何一个调用函数里面, 以此实现调用方法发生异常或者错误时, 可以快速返回, 避免持续请求, 造成资源的耗尽.
Feign 整合 Hystrix
如上小节说示例的, 当我们方法很多时, 要是分别编写一个 fallback 估计也是崩溃的, 虽然可以使用一个通用的 fallback, 但未进行特殊设置下, 也是无法知道具体是哪个方法发生熔断的.
而对于 Feign, 我们可以使用一种更加优雅的形式进行. 我们可以指定 @FeignClient 注解的 fallback 属性, 或者是 fallbackFactory 属性, 后者可以获取异常信息的.
修改 spring-cloud-hystrix 工程.
0. 引入 Feigin 的 POM 依赖.
- <!-- feign -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
1. 启动类, 加入 @EnableFeignClients 启用 Feign.
**
* 熔断器示例
- * @author oKong
- *
- */
- @SpringBootApplication
- @EnableHystrix
- @EnableDiscoveryClient
- @EnableFeignClients
- @Slf4j
- public class HystrixApplication {
- public static void main(String[] args) throws Exception {
- SpringApplication.run(HystrixApplication.class, args);
- log.info("sprign-cloud-hystrix 启动!");
- }
- @Bean
- @LoadBalanced
- public RestTemplate restTemplat() {
- return new RestTemplate();
- }
- }
创建一个服务接口类 IHelloClient.java, 同时定义 fallback 或者 fallbackFactory 属性值. 注意: 两者 同时设置时, 优先调用 fallback,fallbackFactory 不进行调用了.
- @FeignClient(name="eureka-client",/*fallback=HelloClientFailImpl.class,*/ fallbackFactory = HelloClientFallbackFactory.class)
- public interface IHelloClient {
- /**
- * 定义接口
- * @param name
- * @return
- */
- @RequestMapping(value="/hello", method=RequestMethod.GET)
- public String hello(@RequestParam("name") String name);
- }
创建 fallback 和 fallbackFactory 属性对应类.
HelloClientFailImpl.java
- @Component("fallback")
- @Slf4j
- public class HelloClientFailImpl implements IHelloClient{
- @Override
- public String hello(String name) {
- log.error("restTemplate 调用 [hello] 服务发生熔断, 参数 name:{}", name);
- return "restTemplate 调用 [hello] 服务发生熔断, 参数 name:" + name;
- }
- }
- HelloClientFallbackFactory/java
- @Component
- @Slf4j
- public class HelloClientFallbackFactory implements FallbackFactory<IHelloClient>{
- @Autowired
- @Qualifier("fallback")
- IHelloClient helloClient;
- @Override
- public IHelloClient create(Throwable cause) {
- log.error("feign 调用发生异常, 触发熔断", cause);
- return helloClient;
- }
- }
可以知道, 正常 fallback 就是一个接口的实现类, 当发送异常时, 会调用此接口实现类进行服务调用. 而 FallbackFactory 是也是一个接口实现类, 需要实现 feign.hystrix.FallbackFactory<T > 接口, 在发生熔断时, 调用 create 方法, 同时返回被调用接口的实现类, 以便进行 fallback 处理.
3. 配置文件开启 feign 的熔断器功能.
feign.hystrix.enabled=true
或者, 申明一个 Feign.Builder 类也是可以的, 我们从 org.springframework.cloud.openfeign.FeignClientsConfiguration 可以看出, 启用 feign 的条件:
所以正常, 我们只需要在配置文件中加入 feign.hystrix.enabled 为 true 即可, 注意: 此属性在 IDE 下未进行提示的.
或者就如此类一样, 申明一个 bean:
- @Bean
- public Feign.Builder feignHystrixBuilder() {
- return HystrixFeign.builder();
- }
也是可以的.
4. 编写一个测试类 FeignController:
- /**
- * feign 熔断器示例
- * @author oKong
- *
- */
- @RestController
- @Slf4j
- public class FeignController {
- @Autowired
- IHelloClient helloClient;
- @GetMapping("/feign")
- public String hello(String name) {
- log.info("使用 feign 调用服务, 参数 name:{}", name);
- return helloClient.hello(name);
- }
- }
5. 再次启动应用, 访问: http://127.0.0.1:8038/feign?name=oKong , 正常调用如下:
关闭服务提供者, 再次访问, 浏览器返回了错误提示:
同时, 我们使用了 FallbackFactory, 控制台打印出了具体异常:
针对熔断超时时间等相关设置, 可以通过 @HystrixCommand 注解的各属性进行配置, 主要还是 commandProperties 属性值, 具体的参数可查看 com.netflix.hystrix.HystrixCommandProperties 类, 也可以针对某个调用方法进行特殊设置. 具体的可以看看这篇文章: hystrix 的基本介绍和配置属性说明 https://my.oschina.NET/dengfuwei/blog/1621342 , 或者可以去大佬程序员 DD 博客 http://blog.didispace.com/ 查阅下关于 Hystrix 相关知识点: 服务容错保护(Hystrix 断路器)[Dalston 版] , 服务容错保护(Hystrix 依赖隔离)[Dalston 版] , 版本虽然是 D 版的, 但原理是差不多的~
参考资料
总结
本章节主要讲解了如何整合 Hystrix. 本身 Hystrix 已经包含了服务降级, 依赖隔离, 熔断器等功能了, 我们使用时并没进行特殊设置, 默认都是生效的. 对于一些关于 Hystrix 的高级用法, 比如信号量隔离, 线程池的设置等等, 还有一些像超时时间等, 由于此方面了解的不多, 这里就不班门弄斧了. 大家可去官方网站或者谷歌搜索下相关材料下, 对于一些业务场景, 可进行一些自定义设置. 主要还是针对 @HystrixCommand 注解的相关配置. 关于调用统一异常的处理相关实践, 比如当提供方异常时, 调用方如何进行统一异常处理, 或者服务不可用时, 进行统一的异常捕获, 告知外围调用者服务不可用等信息. 这些相关实践部分会在之后的实践篇系列文章中进行阐述的, 也许不是最佳的实践, 仅希望提供一个参考方案吧. 这里就不表了, 敬请期待~
最后
来源: https://www.cnblogs.com/okong/p/springcloud-five.html