Hystrix 源自 Netflix 团队于 2011 年开始研发。2012年 Hystrix 不断发展和成熟,Netflix 内部的许多团队都采用了它。如今,每天在 Netflix 上通过 Hystrix 执行数百亿个线程隔离和数千亿个信号量隔离的调用。极大地提高了系统的稳定性。
在分布式环境中,不可避免地会有许多服务依赖项中的某些服务失败而导致雪崩效应。Hystrix 是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体稳定性。
雪崩效应在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问 A 服务,而 A 服务需要调用 B 服务,B 服务需要调用 C 服务,由于网络原因或者自身的原因,如果 B 服务或者 C 服务不能及时响应,A 服务将处于阻塞状态,直到 B 服务 C 服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。以下图示完美解释了什么是雪崩效应。
当一切服务正常时,请求看起来是这样的:
当其中一个服务有延迟时,它可能阻塞整个用户请求:
在高并发的情况下,一个服务的延迟可能导致所有服务器上的所有资源在数秒内饱和。比起服务故障,更糟糕的是这些应用程序还可能导致服务之间的延迟增加,从而备份队列,线程和其他系统资源,从而导致整个系统出现更多级联故障。
总结造成雪崩的原因可以归结为以下三点:
服务提供者不可用(硬件故障,程序 BUG,缓存击穿,用户大量请求等)
重试加大流量(用户重试,代码逻辑重试)
服务消费者不可用(同步等待造成的资源耗尽)
最终的结果就是:一个服务不可用,导致一系列服务的不可用。
解决方案雪崩是系统中的蝴蝶效应导致,其发生的原因多种多样,从源头我们无法完全杜绝雪崩的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估做好服务容错。解决方案大概可以分为以下几种:
请求缓存:支持将一个请求与返回结果做缓存处理;
请求合并:将相同的请求进行合并然后调用批处理接口;
服务隔离:限制调用分布式服务的资源,某一个调用的服务出现问题不会影响其他服务调用;
服务熔断:牺牲局部服务,保全整体系统稳定性的措施;
服务降级:服务熔断以后,客户端调用自己本地方法返回缺省值。
环境准备eureka-server:注册中心
eureka-server02:注册中心
product-service:商品服务,提供了 /product/{id} 接口,/product/list 接口,/product/listByIds 接口
order-service-rest:订单服务,基于 Ribbon 通过 RestTemplate 调用商品服务
order-server-feign:订单服务,基于 Feign 通过声明式服务调用商品服务
模拟高并发场景服务提供者接口添加 Thread.sleep(2000),模拟服务处理时长。
package com.example.controller; import com.example.pojo.Product; import com.example.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; /** * 查询商品列表 * * @return */ @GetMapping("/list") public List<Product> selectProductList() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return productService.selectProductList(); } /** * 根据多个主键查询商品 * * @param ids * @return */ @GetMapping("/listByIds") public List<Product> selectProductListByIds(@RequestParam("id") List<Integer> ids) { return productService.selectProductListByIds(ids); } /** * 根据主键查询商品 * * @param id * @return */ @GetMapping("/{id}") public Product selectProductById(@PathVariable("id") Integer id) { return productService.selectProductById(id); } }服务消费者降低 Tomcat 最大线程数方便模拟高并发。
server: port: 8080 tomcat: max-threads: 10 # 降低最大线程数方便模拟高并发 JMeter