9.Hystrix 断路器
9.1 概述
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

一般情况对于服务依赖的保护主要有3中解决方案:
(1)熔断模式
:这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
(2)隔离模式
:这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火烧光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
(3)限流模式
:上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
Hystrix
是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”
本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
hystrix-github官网 https://github.com/netflix/hystrix/
停止更新,进入维护期
9.2 重要概念
9.2.1 服务降级 fallback
概念:服务器繁忙,请稍后重试,不让客户端等待并立即返回一个友好的提示。fallback
出现服务降级的情况:
程序运行异常
超时
服务熔断触发服务降级
线程池/信号量打满也会导致服务降级
9.2.2 服务熔断 break
概念: 类比 保险丝
,达到最大访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好的提示。
服务熔断-->服务降级-->恢复调用链路
9.2.3 服务限流 flowlimit
概念: 秒杀高并发等操作,眼睛一窝蜂的过来拥挤,进行排队,一秒中N个,有序进行
9.3. 案例
9.3.1搭建带熔断的服务
首先使用Eureka单机版搭建正常的开发环境
- 创建项目,具体信息如下
Title | 值 | 备注 |
---|---|---|
artifactId | 11-cloud-provider-hystrix-order-8001 | |
groupId | org.jshand.cloud | |
version | 1.0-SNAPSHOT |
- 修改pom
server:
port: 8001
spring:
application:
name: cloud-order-hystrix-serice
eureka:
instance:
hostname: 97.0.0.1
instance-id: cloud-order-hystrix-sericee-8001
prefer-ip-address: on
client:
serviceUrl:
defaultZone: http://server8760.com:8760/eureka/
logging:
level:
org.jshand.cloud: debug
- 主启动类
package org.jshand.cloud;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 带熔断器的微服务 主启动类
*/
@SpringBootApplication
@Slf4j
public class ProviderHystrixOrderApp8001 {
public static void main(String[] args) {
SpringApplication.run(ProviderHystrixOrderApp8001.class, args);
}
}
定义Service层代码,一个正常返回,一个延迟3秒返回
javapackage org.jshand.cloud.service; import org.springframework.stereotype.Service; @Service public class OrderService { /** * 立即处理完成并返回,模拟快速返回 * @param id * @return */ public String getOrderOk(Integer id){ return String.format("微服务(OK) 线程:%s,查询订单id:%d,(* ̄︶ ̄)", Thread.currentThread().getName(), id ); } /** * 延迟3秒的 模拟长时间执行的业务 * @param id * @return */ public String getOrderTimeout(Integer id){ int timesecond = 3; try { Thread.sleep(timesecond); } catch (InterruptedException e) { e.printStackTrace(); } return String.format("微服务(Timeout(秒):%d, 线程:%s,查询订单id:%d,(* ̄︶ ̄)", timesecond, Thread.currentThread().getName(), id ); } }
定义Controller
package org.jshand.cloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.jshand.cloud.service.OrderService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderController {
@Resource
OrderService orderService;
/**
* 正常的
* http://127.0.0.1:8001/provider/hystrix/getOrderOk/1
* http://127.0.0.1:8001/provider/hystrix/getOrderOk/2
* http://127.0.0.1:8001/provider/hystrix/getOrderOk/3
* @param id
* @return
*/
@RequestMapping("/provider/hystrix/getOrderOk/{id}")
public String getOrderOk(@PathVariable Integer id){
String result = orderService.getOrderOk(id);
log.info("************"+result);
return result;
}
/**
* 延迟返回的
* http://127.0.0.1:8001/provider/hystrix/getOrderTimeout/1
* http://127.0.0.1:8001/provider/hystrix/getOrderTimeout/2
* http://127.0.0.1:8001/provider/hystrix/getOrderTimeout/3
* @param id
* @return
*/
@RequestMapping("/provider/hystrix/getOrderTimeout/{id}")
public String getOrderTimeout(@PathVariable Integer id){
String result = orderService.getOrderTimeout(id);
log.info("************"+result);
return result;
}
}
9.3.2 使用Jmeter压测
使用Apache-Jmeter工具模拟大规模访问getOrderTimeout
[x] 下载Jmeter
[x] 配置线程组 20000 ( 2W)个线程, 视服务器配置可以适当调整
[x] 设置http请求路径
- 创建http请求
- 设置路径
测试
- 启动jmeter压测后,发现浏览器正常访问的路径 http://127.0.0.1:8001/provider/hystrix/getOrderOk/1也会变慢。
此时如果是consumer客户端发起的请求,可能导致服务超时,无法使用
。
- 启动jmeter压测后,发现浏览器正常访问的路径 http://127.0.0.1:8001/provider/hystrix/getOrderOk/1也会变慢。
卡顿原因
- 由于压力测试工具大量消耗Tomcat线程池,导致服务器无法即时的处理
getOrderOk
的请求
- 由于压力测试工具大量消耗Tomcat线程池,导致服务器无法即时的处理
9.3.3 创建消费方(用户中心)
key | value | remark |
---|---|---|
groupId | org.jshand.cloud | |
artifactId | 12-cloud-consumer-hystrix-order-80 | |
version | 1.0-SNAPSHOT |
- 修改pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-202101</artifactId>
<groupId>org.jshand.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>12-cloud-consumer-hystrix-membercenter-80</artifactId>
<dependencies>
<!--openfeign 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--Eureka Client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--lombok工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--spring-web工具-->
<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>
<!-- springboot开发工具 、热部署 建议热部署时使用,optional为true意味着依赖不会传递给外部-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
- 修改application.yml
server:
port: 80
spring:
application:
name: cloud-member-center--hystrix-service
eureka:
client:
serviceUrl:
defaultZone: http://server8760.com:8760/eureka
instance:
hostname: 127.0.0.1
instance-id: cloud-member-center--hystrix-service-80
#注册中心中点击超链接时使用ip地址
prefer-ip-address: true
logging:
level:
#设置service目录 日志级别为 debug
org.jshand.cloud.service: debug
- 主启动类
package org.jshand.cloud;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 使用hystrix熔断器的客户端 app启动类
*/
@SpringBootApplication
@Slf4j
@EnableFeignClients
public class MemberCenterHystrixApp80 {
public static void main(String[] args) {
SpringApplication.run(MemberCenterHystrixApp80.class, args);
}
}
- 业务接口(Service)
package org.jshand.cloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Component
@FeignClient(name="CLOUD-ORDER-HYSTRIX-SERICE")
public interface OrderService {
@RequestMapping("/provider/hystrix/getOrderOk/{id}")
public String getOrderOk(@PathVariable Integer id);
@RequestMapping("/provider/hystrix/getOrderTimeout/{id}")
public String getOrderTimeout(@PathVariable Integer id);
}
- 定义Controller
package org.jshand.cloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.jshand.cloud.service.OrderService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class MemberCenterHystrixController {
@Resource
private OrderService orderService;
/**
* http://127.0.0.1:80/consumer/hystrix/getOrderOk/100
* @param id
* @return
*/
@RequestMapping("/consumer/hystrix/getOrderOk/{id}")
public String getOrderOk(@PathVariable Integer id){
String result = orderService.getOrderOk(id);
log.info("******** result:"+result);
return result;
}
/**
* http://127.0.0.1:80/consumer/hystrix/getOrderTimeout/100
* @param id
* @return
*/
@RequestMapping("/consumer/hystrix/getOrderTimeout/{id}")
public String getOrderTimeout(@PathVariable Integer id){
String result = orderService.getOrderTimeout(id);
log.info("******** result:"+result);
return result;
}
}
jmeter压测+客户端访问。consumer客户端出现同样的问题访问 http://127.0.0.1:80/consumer/hystrix/getOrderOk/100,同样出现转圈圈,甚至可能出现TImeout的情况.
9.4
正是因为有上述的问题,所以才需要我们引入服务降级、容错、限流等技术。
超时导致服务器变慢(转圈)超时不再等待。
出错(宕机或服务运行出错),要有友好的提示,出错的解决方案。而不是直接报错错误。
- 对方服务(provider)超时,调用者(consumer)不能一直等待,需要服务降级
- 对方服务(provider)宕机了,调用者(consumer)不能直接报错,需要服务降级
- 对方服务(provider)ok,调用者(consumer)自己出故障了或者自己有要求(自己的等待时间小于服务提供者),自己需要服务降级。
9.5 服务降级 fallback
9.5.1 服务侧进行处理
1)在主启动类上 激活Hystrix,添加@EnableHystrix或者@EnableCircuitBreaker注解
package org.jshand.cloud;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
/**
* 带熔断器的微服务 主启动类
*/
@SpringBootApplication
@Slf4j
@EnableHystrix
public class ProviderHystrixOrderApp8001 {
public static void main(String[] args) {
SpringApplication.run(ProviderHystrixOrderApp8001.class, args);
}
}
2)使用需要进行降级的程序上添加@HystrixCommand注解,进行处理
@HystrixCommand(
fallbackMethod = "timeoutHandler",
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="3000")
})
fallbackMethod 降级后调用的方法。
commandProperties 设置降级属性
- execution.isolation.thread.timeoutInMilliseconds 为超时时间,默认为1
- 更多属性配置,参考https://github.com/Netflix/Hystrix/wiki/Configuration
PS: 修改注解,
尽量自己手动重启,不要热部署
,防止一些devtools对注解热部署不生效。
3)添加降级处理方法timeoutHandler
public String timeoutHandler(Integer id){
return "服务异常或超时,请稍后再试";
}
4)除上述超时外也支持服务报错,可以手动将程序设置一个异常后同样也会调用降级的方法
9.5.2 消费侧处理
- 开启Feign支持hystrix,由于consumer消费侧使用OpenFeign调用,需要开启Feign支持hystrix。在yaml文件中添加如下配置:
feign:
hystrix:
enabled: true
2) 主启动类添加激活hystrix注解 @EnableHystrix
或@EnableCircuitBreaker
3)同消费侧处理方式一样,使用@
9.4 工作流程
9.5 服务监控hystrixDashBoard
