前言: 目前我们的项目是微服务架构, 基于 dubbo 框架, 服务之间的调用是通过 rpc 调用的. 刚开始没有任何问题, 项目运行健康, 良好. 可是过了一段时间, 线上总有人反应查询订单失败, 等过了一段时间才能查到. 这是怎么回事呢? 打开后台的日志一看出现了一些 RpcException 和 TimeOutException, 原来是远程调用超时了, 可能某个服务在请求的高发期访问数据库异常, IO 阻塞, 返回接口异常了. 后来这个问题越来越频繁, 如何解决这个棘手的问题呢?
本篇博客的目录
一: Hystrix 是什么?
1.1: 基本解释
Hystrix 最开始由 Netflix(看过美剧的都知道, 它是一个美剧影视制作的巨头公司) 开源的, 后来由 Spring Cloud Hystrix 基于这款框架实现了断路器, 线程隔离等一系列服务保护功能, 该框架的目标在于通过控制访问远程系统, 服务和第三方库的节点, 从而延迟和故障提供更强大的容错能力. hystrix 具备服务降级, 服务熔断, 线程和信号隔离, 请求缓存, 请求合并以及服务监控等强大功能. 起到了微服务的保护机制, 防止某个单元出现故障. 从而引起依赖关系引发故障的蔓延, 最终导致整个系统的瘫痪.
1.2: 断路器的概念
断路器本身是一个开关装置, 用在电路上保护线路过载, 当线路中有电器发生短路的时候."断路器" 能够及时切断故障, 防止发生过载, 发热甚至起火等严重后果. 当分布式架构中, 断路器模式起到的作用也是类似的. 当某个服务发生故障的时候, 通过断路器的故障监控向调用方返回一个错误响应, 而不是长时间的线程挂机, 无限等待. 这样就不会使线程因故障服务被长时间占用不释放, 避免了故障在分布式系统中的蔓延. 如下图是现实中的断路器, 它是一个开关装置:
二: Hystrix 解决超时问题
2.1: 问题
假设我们前端提供了用户查询订单的功能, 首先请求映射到 OrderController, 控制器通过调用服务 orderService 获取订单信息, 前端传过来两个参数: 一个是订单 id, 一个是用户 id,orderService 需要通过用户 id 调取用户服务来获取用户的相关信息返回给订单服务去组装信息, 假设这里是通过 http 请求的, 我们有一个单独的工程叫做: userService 部署在其他的服务器上. 但是这个服务器宕机了, 这时候订单服务调取用户信息就失败了, 然后查询订单整个请求就失败了! 由一个服务的宕机就导致整个查询都失败了, 牵一发而动全身. 流程见下图:
2.2: 使用 Hystrix 进行服务降级
2.2.1: 引入 hystrix 依赖 这里引入了 spring-cloud-starter-netflix-hystrix,springboot 的 starter 里面整合了 hystrix
- <properties>
- <java.version>1.8</java.version>
- <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
- </dependency>
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>4.5.1</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring-cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
2.2.2: 模拟订单请求
首先通过 OrderController 映射 / order 请求, 获取前端传入的参数 orderId 和 useId, 然后调用 orderDetailService 方法,
- @RestController
- public class OrderController {
- @Resource
- private OrderService orderService;
- /**
- * 获取订单信息
- *
- * @param orderNo
- * @return
- */
- @PostMapping("/order")
- public ResultVo<OrderDetail> getOrderInfo(@RequestParam("orderId") Long orderNo, @RequestParam("userId") Long userId) {
- OrderDetail orderDetail = orderService.orderDetailService(orderNo, userId);
- ResultVo resultVo = new ResultVo<>();
- resultVo.setCode(100);
- resultVo.setMessage("请求成功");
- resultVo.setData(orderDetail);
- return resultVo;
- }
- }
2.2.3: 订单服务调取其他服务
这里引入了 RestTemplate, 它是一个 spring 封装的 http 映射请求工具类, 然后通过 http 请求访问 url = "http://192.168.80.153:8070/user/getUser" 获取用户名, 将值给订单对象. 不过假如在这其中发生了调用异常, 请求用户服务异常的话, 那么返回给前端就是一串空的订单信息, 导致用户看到的订单为空. 在使用 hystrix 之后, 可以用 @HystrixCommand(fallbackMethod = "orderFallBack") 注解, 在 fallbackMethod 中指定回退的方法, 这里必须注意在 @HystrixCommand 上的方法其指定的回调方法必须和原方法的参数保持一致, 这里包括参数类型, 参数个数, 参数顺序. 我们在回调用法中模拟去查询缓存数据, 返回给订单. 有人又要问了, 如果查询缓存服务器再异常呢? 不排除这种可能性. 如果是这样的话, 依然可以使用 @HystrixCommand 注解在回调方法中, 再指定其他的回调方法:
- @Service
- public class OrderService {
- @Autowired
- private RestTemplate restTemplate;
- /**
- * 根据订单 id 获取订单详情
- *
- * @param orderId
- * @param userId
- * @return
- */
- @HystrixCommand(fallbackMethod = "orderFallBack")
- public OrderDetail orderDetailService(Long orderId, Long userId) {
- if (Objects.isNull(orderId)) {
- return null;
- }
- OrderDetail orderDetail = OrderDBSource.getOrderDB().get(orderId);
- // 调用 user 服务
- final String url = "http://192.168.80.153:8070/user/getUser";
- String userName = "";
- ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, userId, String.class);
- String returnContent = responseEntity.getBody();
- if (Objects.nonNull(responseEntity) && StrUtil.isNotEmpty(returnContent)) {
- userName = returnContent;
- }
- if (ObjectUtil.isNotNull(orderDetail)){
- orderDetail.setUserName(userName);
- }
- return orderDetail;
- }
- /**
- * 异常调用的回调方法
- *
- * @return
- */
- public OrderDetail orderFallBack(Long orderId, Long userId) {
- OrderDetail orderDetail = OrderDBSource.getOrderCache().get(orderId);
- final String unknown = "未知用户";
- orderDetail.setUserName(unknown);
- return orderDetail;
- }
- }
2.3.4: 模拟测试
为了方便测试, 首先我们将请求服务暂时先注释, 然后用 postman 测试看正常的返回应该是这样的, 这里使用了备注为数据库获取的订单, 表明它没有走回调方法, 因为这里没有访问用户 url 获取用户信息, 程序可以正常访问. 我再放开
加上获取用户服务的链接, 实际上用户服务是无法访问到的, 访问的话就会超时, 超时会被 hystrix 捕捉到, 然后走 fallBack 指定的方法, 我们来测试一下, 可以看到实际上走的是缓存中查询到的订单, 可以看到用户服务已经成功的降级了, 降级后的订单信息虽然是缓存获取到的, 可能会存在延时等问题 (当然只要维护好缓存就可以避免这个问题). 但是比没有任何数据带来的用户一点会更好!
三: Hystrix 的流程
Hystrix 实际上的工作原理是这样的: 通过 command 来解耦请求与返回操作, 在具体的实例中就是, Hystrix 会对依赖的服务进行观察, 通过 command.toObservable 调用返回一个观察的对象, 同时发起一个事件, 然后用 Subscriber 对接受到的事件进行处理. 在 command 命令发出请求后, 它通过一系列的判断, 顺序依次是缓存是否命中, 断路器是否打开, 线程池是否占满, 然后它才会开始对我们编写的代码进行实际的请求依赖服务的处理, 也就是 Hystrix.run 方法, 如果在这其中任一节点出现错误或者抛出异常, 它都会返回到 fallback 方法进行服务降级处理, 当降级处理完成之后, 它会将结果返回给, 际的调用者, 经过一系列流程处理的, 它的具体工作流程如下:
四: 总结
本篇博客讲述了 Hystrix 是什么? 然后解释了 Hystrix 如何进行服务降级处理以及简单的处理流程, 讲到的内容是最为常用的功能, 还有一些关于 Hystrix 的缓存, 线程池的隔离技术等由于篇幅的原因, 没有详细的讲解到, 不过作为一篇入门级的 Hystrix 教程博客是基本够的. 在实际的开发中, 如何保持服务的健壮性, 服务的可用性, 尽量的减少 bug, 提升用户体验都是我们开发者的使命, 这条优化和提升之路永远没有尽头, go ahead!
参考资料《spring cloud 微服务实战》
* 如果你在本篇博客中, 有任何疑问, 都可以添加 java 学习交流群: 618626589
来源: https://www.cnblogs.com/wyq178/p/10455195.html