1、Hystrix入门介绍
1.1 分布式系统面临的问题1.2 Introduction to Hystrix 2、构建服务提供模块8001
2.1 业务类新增异常返回 3、构建服务消费模块80
3.1 建module3.2 改pom3.3 写yml3.4 主启动3.5 业务类3.6 测试
3.6.1 测试查看订单接口:getPaymentById3.6.2 测试长时间业务处理接口:PaymentFeignTimeout 1、Hystrix入门介绍 1.1 分布式系统面临的问题
前面已经构建了erureka集群(7001,7002)、服务提供模块集群(8001,8002)、和消费服务模块(80)的简单demo。
但是在实际的分布式系统中,一般不是和demo这样简单的调用依赖关系,而是多级的服务调用关系,但是这些服务之间的相互调用在某些时候不可避免地会出现失败。
比如微服务A调用微服务B和微服务C,微服务B又调用微服务D,微服务C又调用微服务E。如果微服务D的调用时间过长或者已经不可用了。此时,A调用C,C调用D,在D这里调用时间长或不可用的话,会导致该服务其实一致没有返回。但是在高流量的情况下,微服务A还会继续接收到流量,此时前面的服务都还没有处理完响应。因此对微服务A的持续调用就会占用越来越多的系统资源,导致延迟增加,备份队列,线程和其他系统资源紧张,从而导致整个系统发生更多的级联故障,进而引起系统崩溃,发生所谓的“雪崩效应”。
针对上面分布式系统中遇到的问题,本节介绍Hystrix。首先访问官网,看下介绍:https://github.com/Netflix/Hystrix。
Hystrix 是一个用于处理分布式系统的延迟(latency)和容错(tolerance)的开源库,旨在隔离对远程系统、服务和第 3 方库的访问点,停止级联故障(cascading failure),使故障不可避免的复杂分布式系统具有弹性(enable resilience)。
也就是当某个服务发生故障后,Hystrix可以向调用方返回一个预期的、可处理的备选响应(FallBack),而不是长时间等待或者抛出调用方法无法处理的异常,从而保证服务调用方的线程不会长时间地占用,从而避免故障在分布式系统中的蔓延,乃至雪崩。
还是和之前一样,构建注册进eureka集群的服务提供模块8001。
服务降级主要在以下4种情况下会发生:
程序运行异常超时服务熔断触发服务降级线程池/信号量打满导致服务降级 2.1 业务类新增异常返回
在服务提供模块(8001)中的查看订单的业务逻辑中,如果id为小于等于0,那么就抛出异常。
package com.example.springcloud.controller;import com.example.springcloud.entities.CommonResult;import com.example.springcloud.entities.Payment;import com.example.springcloud.service.PaymentService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.util.concurrent.TimeUnit;@RestController@Slf4jpublic class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("插入结果" + result); if (result > 0) { return new CommonResult(200, "插入数据库成功, serverPort: " + serverPort, result); } else { return new CommonResult(500, "插入数据失败, serverPort: " + serverPort); } } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) throws Exception{ if (id<=0) { throw new Exception("订单编号必须大于0"); } Payment payment = paymentService.getPaymentById(id); log.info("读取订单: " + payment); if (payment==null) { return new CommonResult(500, "未查询到该订单,查询id: " + id); } else { return new CommonResult(200, "查询成功, serverPort: " + serverPort, payment); } } @GetMapping(value = "/payment/feign/timeout") public String PaymentFeignTimeout() { // 模拟较长时间的业务处理,时间为3秒 int timeOut = 3; try { TimeUnit.SECONDS.sleep(timeOut); } catch (InterruptedException e) { e.printStackTrace(); } return "长时间业务处理:" + serverPort; }}
3、构建服务消费模块80 3.1 建module 构建一个包含服务降级hystrix的服务消费模块:cloud-consumer-order-feign-hystrix-80。改模块的环境构建可参考cloud-consumer-order-feign-80工程。
依赖包新增hystrix:spring-cloud-starter-netflix-hystrix。
<?xml version="1.0" encoding="UTF-8"?>
在配置文件中开启feign-hystrix属性。
server.port=8080spring.application.name=cloud-order-service# 添加Eureka Client配置# 表示是否将自己注册进Eureka Server,默认为trueeureka.client.register-with-eureka=true# 表示是否从Eureka Server抓取已有的注册信息,默认为true。# 该配置在单节点时无所谓,集群配置时必须设置为true,才能配合ribbon使用负载均衡eureka.client.fetchRegistry=true#eureka.client.service-url.defaultZone=http://localhost:7001/eureka # 单机版eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版# 建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间ribbon.CoonecTimeout=5000# 建立连接后从服务器读取到可用资源所用的时间ribbon.ReadTimeout=5000# 指定feign日志以什么级别监控哪个接口logging.level.com.example.springcloud.service.PaymentFeignService=debug# 开启feign-hystrix属性feign.hystrix.enabled=true
3.4 主启动启动类上加上启动Hystrix的注解:@EnableHystrix
package com.example.springcloud;import cn.hutool.db.sql.Order;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.hystrix.EnableHystrix;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication@EnableFeignClients@EnableHystrixpublic class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class, args); }}
3.5 业务类 构建openfeign支付服务接口,直接复制服务提供模块的controller即可。
两个接口:
1)getPaymentById:根据id查询订单
2)PaymentFeignTimeout:长时间业务处理接口,业务处理时间为3秒
package com.example.springcloud.service;import com.example.springcloud.entities.CommonResult;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.stereotype.Component;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@Component@FeignClient(value = "cloud-payment-service")public interface PaymentHystrixService { @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id); @GetMapping(value = "/payment/feign/timeout") public String PaymentFeignTimeout();}
构建订单服务controller,复制PaymentHystrixService即可,接口路径上添加/consumer表示是服务消费接口。
针对服务端可能出现的长时间调用或者服务异常的情况,给每个接口添加对应的服务降级方法。
当服务2秒后未返回,或者服务发生异常,80服务接口直接调用备选方法(fallback method)返回。
package com.example.springcloud.controller;import com.example.springcloud.entities.CommonResult;import com.example.springcloud.service.PaymentHystrixService;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController@Slf4jpublic class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping(value = "/consumer/payment/get/{id}") @HystrixCommand( fallbackMethod = "getPaymentByIdFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") } ) public CommonResult getPaymentById(@PathVariable("id") Long id) { return paymentHystrixService.getPaymentById(id); } @GetMapping(value = "/consumer/payment/feign/timeout") @HystrixCommand( fallbackMethod = "PaymentFeignTimeoutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") } ) public String PaymentFeignTimeout() { return paymentHystrixService.PaymentFeignTimeout(); } // getPaymentById接口的服务降级方法 public CommonResult getPaymentByIdFallbackMethod(@PathVariable("id") Long id) { String msg = "getPaymentById接口繁忙,请稍后再试,当前处理线程:" + Thread.currentThread().getName(); return new CommonResult(500, msg); } // PaymentFeignTimeout接口的服务降级方法 public String PaymentFeignTimeoutFallbackMethod() { return "PaymentFeignTimeout接口繁忙,请稍后再试,当前处理线程:" + Thread.currentThread().getName(); }}
3.6 测试 3.6.1 测试查看订单接口:getPaymentById 首先测试没有异常的情况,也就是id大于0的情况:http://localhost:8080/consumer/payment/get/3
然后测试有异常的情况,也就是id小于等于0的情况:http://localhost:8080/consumer/payment/get/-3。
可以看到当服务提供接口发生异常的时候,80接口直接返回备选方法的返回结果,而不会返回服务提供接口的异常返回信息。
由于在8001接口中,我们设置的长时间业务处理有3秒。而在服务消费模块80的controller这里设置的超时时间为2s。因此调用消费服务模块的接口一定会返回备选方法的返回值。
服务消费接口:http://localhost:8080/consumer/payment/feign/timeout
根据上面的测试,我们可以得到,当服务异常或者超时的情况下,可以发生服务降级,也就是返回该服务的备选方法的结果,避免程序发生级故障,导致系统崩溃。