1、概念
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。“断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝) ,向调用方返回一个符合预期的、可处理的备选响应(FallBack ,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务降级:
服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback
服务降级情况:
- 程序运行异常
- 超时
- 程序熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
服务熔断:
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务限流:
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
2、如何使用
新建cloud-hystrix-pay-8007模块
<dependencies>
<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.yu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 8007
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url: http://eureka7001.com:7001/eureka
主启动类
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8007 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8007.class, args);
}
}
业务类
@Service
public class PaymentService {
/**
* 正常访问,肯定ok
*
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池: " + Thread.currentThread().getName() + " PaymenyInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~";
}
/**
* paymentInfo_TimeOut
*
* @param id
* @return
*/
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 3;
try {
TimeUnit.MILLISECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: " + Thread.currentThread().getName() + " PaymenyInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时" + timeNumber + "毫秒";
}
}
controller
@RestController
@Slf4j
public class paymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("*****result"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result"+result);
return result;
}
}
测试
此时使用压测工具(Jmeter),并发20000个请求,请求会延迟的那个方法,
在压力测试中,发现另外一个方法并没有被压测,但是我们访问它时,却需要等待
这就是因为被压测的方法它占用了服务器大部分资源,导致其他请求也变慢了
创建cloud-hystrix-order-80模块
pom
<dependencies>
<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.yu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
server:
port: 8090
eureka:
client:
#是否将自己注册到注册中心, 默认true
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka #单机版
ribbon:
ReadTimeout: 15000
ConnectTimeout: 15000
主启动类
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class);
}
}
远程调用提供者模块的接口
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
controller
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
测试
启动eureka7001,提供者8007和消费者,http://localhost:8090/consumer/payment/hystrix/timeout/1
在高并发压力测试,发现访问没设定超时的方法访问速度也变慢了,因为8007同一层次的其他接口服务被困死,tomcat线程池里面的工作线程已经被挤占完毕,这个时候8090调用8007,客户端访问会响应缓慢。可以配置服务降级来解决。
配置服务降级
在服务端和客户端都可以配置,但一般服务降级都配置在客户端,先修改配置文件
feign:
hystrix:
enabled: true
在主启动类上添加@EnableHystrix注解
修改controller
测试
重构
上面出现的问题:
1,降级方法与业务方法写在了一块,耦合度高
2.每个业务方法都写了一个降级方法,重复代码多
解决重复代码的问题:
配置一个全局的降级方法,所有方法都可以走这个降级方法,至于某些特殊创建,再单独创建方法
修改conreoller
测试
解决耦合度(基于order模块)
-
PaymentHystrixService接口是远程调用pay模块的,可以创建一个PaymentFallbackServiceImpl类实现此接口,在实现类中统一异常处理
@Component public class PaymentFallbackServiceImpl implements PaymentHystrixService { @Override public String paymentInfoOk(Integer id) { return "-------PaymentFallbackService fall back-[paymentInfo_OK],o(╥﹏╥)o"; } @Override public String paymentInfoTimeOut(Integer id) { return "-------PaymentFallbackService fall back-[paymentInfo_TimeOut],o(╥﹏╥)o"; } }
-
让PaymentHystrixService的实现类生效
服务熔断
修改cloud-hystrix-pay-8007模块
-
修改PaymentService接口
/** * 服务的降级->进而熔断->恢复调用链路 * * @param id * @return */ @HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallback", commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //请求次数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //时间窗口期 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),//失败率达到多少后跳闸 }) public String paymentCircuitBreaker(@PathVariable("id") Integer id) { if (id < 0) { throw new RuntimeException("******id 不能为负数"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber; } public String paymentCircuitBreakerFallback(@PathVariable("id") Integer id) { return "id 不能负数,请稍后再试,o(╥﹏╥)o id:" + id; }
-
修改controller
/** * 服务熔断 * * @param id * @return */ @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("------------result: " + result); return result; }
-
修改主启动
-
测试
启动eureka7001,启动当前模块,先输入正数Id,访问成功,然后连续输入负数id多次(这里属性整体意思是:10秒之内 (窗口,会移动),如果并发==超过==10个,或者10个并发中,失败了6个,就开启熔断器),当断路器开启后输入正数id 发现也是错误页面,过一会后(默认5秒),服务自动恢复,访问正数id页面正常
总结:
服务监控
新建cloud-consumer-hystrix-dashboard9001
pom
<dependencies>
<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.yu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!--监控-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
yml
server:
port: 9001
主启动
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
修改cloud-hystrix-pay-8007的主启动
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8007 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8007.class, args);
}
/**
* 此配置是为了服务监控而配置,与服务器容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream
* 只要在自己的项目里配置上下文的servlet就可以了
*/
@Bean
public ServletRegistrationBean getservlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
测试
- 启动eureka7001
- 启动服务监控9001
- 启动提供者8007
- 浏览器输入http://localhost:9001/hystrix
- 访问http://localhost:8007/payment/circuit/1,然后点击上图的Monitor Stream即可看见
- 断路器启动
- 正常状态
如何看图
实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康程度从绿色-黄色-橙色-红色递减。该实心圆除了颜色的变化外,它的大小也会根据实例的请求量发生变化,流量越大该实心圆也就越大。所以通过实心圆的展示,可以在大量的实例中快速的发现故障实例和高压力实例。
**曲线:**用来记录两分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势
整图说明