0. 关于Hystrix
Hystrix是Netflix的一个组件,其本身已经处于维护状态;Hystrix的仪表盘Hystrix Dashboard已经停止维护,处于启用状态,对于服务熔断,可以使用sentinel组件。本篇主要介绍微服务中的一些重要概念,以及Hystrix组件的基本使用。
1. 微服务中的重要概念
1.1 服务雪崩
在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程。
图解:
如存在如下调用链路:
而此时,Service A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算Service A能扛得住请求,Service B和Service C未必能扛得住这突发的请求。此时,如果Service C因为抗不住请求,变得不可用。那么Service B的请求也会阻塞,慢慢耗尽Service B的线程资源,Service B就会变得不可用。紧接着,Service A也会不可用,最终导致整个调用链路不可用,这一过程如下图所示
在集群部署的情况下,一个节点不可用,由于负载均衡,之后的请求会被分发到剩余可用的节点,导致他们负担加重,进而可能导致整个服务都不可用。
1.2 服务熔断
如何解决微服务系统的服务雪崩问题?——服务熔断
服务熔断——Hystrix——保险丝/熔断器/断路器/监控器
作用:用来在微服务系统中防止服务雪崩现象出现的
熔断机制:所有微服务中必须引入Hystrix组件(不同微服务有自己的Hystrix)
“熔断器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器(hystrix)的故障监控,某个异常条件被触发,直接熔断整个服务(集群中的一个节点)。向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,就保证了服务调用方的线程不会被长时间占用,避免故障在分布式系统中蔓延,乃至雪崩。如果目标服务情况好转则恢复调用。服务熔断是解决服务雪崩的重要手段。
断路器
打开
- 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
到达以上阀值(同时满足条件1和2),断路器将会开启;当断路器开启的时候,所有请求都不会进行转发
关闭
- 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5。
面试
面试重点问题: 断路器流程
1.3 服务降级
服务压力剧增的时候根据当前的业务情况及流量对一些服务(边缘)和页面有策略的降级,以此缓解服务器的压力,以保证核心任务的进行。同时保证部分甚至大部分任务客户能得到正确的响应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。
服务降级是站在整个系统架构的角度,对服务的保护;服务熔断是站在服务调用链,对链路中服务的保护。
举例:双十一的时候订单量特别大,订单服务可能负荷非常大,我们可以有策略地关闭一些订单相关的边缘服务的,比如:修改查询所有订单为只查询前100条;关闭订单评价功能…来保证下单服务可以正常运作。
1.4 降级和熔断总结
共同点
- 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
- 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
- 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
- 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;sentinel
不同点
- 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
- 管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务边缘服务开始)
总结
- 熔断必会触发降级,所以熔断也是降级一种,区别在于熔断是对调用链路的保护,而降级是对系统过载的一种保护处理
2. Hystrix(维护状态)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cerWCZG-1649011570669)(https://cdn.jsdelivr.net/gh/zewei94yomi/ImageLoader@master/uPic/pkNXi7.jpg)]
2.1 简介
通俗定义: Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调用失败,超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障(服务雪崩现象),提高分布式系统的弹性。
作用:用来防止微服务系统中服务雪崩现象,实现服务熔断
2.2 使用
服务端

假设现在有这样一个场景:“用户服务”会调用“商品服务”,“商品服务”会调用“库存服务”。我们希望给“库存服务”添加一个Hystrix组件来监控并实现服务熔断功能。
-
这里出于演示目的,创建一个单独项目
springcloud_08_hystrix
-
引入Hystrix依赖(希望哪个服务被监控,就引入该依赖;注意是netflix下的hystrix)
<dependencies> <!--springboot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--consul--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!--actuator--> <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> </dependencies>
-
配置文件
server.port=9998 spring.application.name=HYSTRIX # consul server 服务注册地址 spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 # 指定当前注册的服务的服务名,默认引用:spring.application.name spring.cloud.consul.discovery.service-name=${spring.application.name}
-
入口类
@SpringBootApplication //@EnableDiscoveryClient // 开启服务注册:可不写 @EnableCircuitBreaker // 开启Hystrix服务熔断监控 public class HystrixApplication { public static void main(String[] args) { SpringApplication.run(HystrixApplication.class, args); } }
-
模拟服务出错
@RestController public class DemoController { @GetMapping("/demo") // ?id= public String demo(Integer id) { System.out.println("Demo OK!"); if (id < 0) { throw new RuntimeException("Invalid ID"); } return "Demo OK!"; } }
这样可以通过浏览器来模拟微服务调用失败
回顾断路器开启条件:
-
快速失败方法
@RestController public class DemoController { @GetMapping("/demo") // ?id= @HystrixCommand(fallbackMethod = "demoFallBack") // 熔断之后的处理,'fallbackMethod': 快速失败的方法名 public String demo(Integer id) { System.out.println("Demo OK!"); if (id < 0) { throw new RuntimeException("Invalid ID"); } return "Demo OK!"; } // 快速失败方法,要求:参数列表和返回值必须与原方法一致,方法名无特殊要求 public String demoFallBack(Integer id) { return "当前服务已熔断"; } }
-
测试:
我们规定
id>=0
时服务正常,id<0
时抛出异常(模拟服务调用失败)当以正确方式请求:
http://localhost:9998/demo?id=1
,得到结果:当以错误方式请求:
http://localhost:9998/demo?id=-6
,得到结果:使用错误请求调用10次后,使用正确请求调用:
至此,就实现了服务端的服务熔断。
如果为每一个服务方法开发一个降级,对于我们来说,可能会出现大量的代码的冗余,不利于维护,这个时候就需要加入默认服务降级处理方法。所以总结起来:
在实战过程中断路器使用:
a. 为每一个调用接口提供自定义备选处理(自定义的优先级高于默认的)
@GetMapping("/product/hystrix")
@HystrixCommand(fallbackMethod = "testHystrixFallBack") //通过HystrixCommand降级处理 指定出错的方法
public String testHystrix(String name) {
log.info("接收名称为: " + name);
int n = 1/0;
return "服务[" + port + "]响应成功,当前接收名称为:" + name;
}
// 指定的处理方法
public String testHystrixFallBack(String name) {
return port + "当前服务已经被降级处理!!!,接收名称为: "+name;
}
b. 使用Hystrix提供的默认备选处理,因为不同的方法参数可能不同,所以在默认的处理方法中就不写参数
@GetMapping("/product/hystrix")
@HystrixCommand(defaultFallback = "defaultFallback")
public String testHystrix(String name) {
log.info("接收名称为: " + name);
int n = 1/0;
return "服务[" + port + "]响应成功,当前接收名称为:" + name;
}
// 默认的处理方法
public String defaultFallback() {
return "当前网络连接失败,请重试";
}
客户端
-
前面实现的是服务端的服务熔断,而客户端应对服务熔断,乃至之后的服务降级,也应该有自己的应对手段(FallBack方法)
-
在我们的例子中,即虽然库存服务已经被熔断,但是商品服务还是会通过openfeign不断去调用库存服务。所以我们应该在商品服务(客户端)也实现快速失败方法,为应对服务端的服务熔断和服务降级。
这里我们创建一个demo的客户端,并通过OpenFeign来实现服务调用
-
创建一个客户端项目
springcloud_08_hystrix_openfeign
-
引入依赖:注意OpenFeign底层已经集成了Hystrix,所以无需再显式地引入该依赖
<dependencies> <!--springboot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--consul--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!--actuator--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
-
配置文件:端口
9999
,服务名HYSTRIXOPENFEIGN
… -
入口类:
@SpringBootApplication
,@EnableFeignClients
-
Controller
@RestController public class DemoController { @Autowired private HystrixClient hystrixClient; @Value("${server.port}") private int port; @GetMapping("/demo") public String demo() { System.out.println("HystrixOpenFeignApplication Demo OK!"); String result = hystrixClient.demo(123); System.out.println("远程调用结果:" + result); return "HystrixOpenFeignApplication Demo OK! port = " + port; } }
-
OpenFeign接口:HystrixClient
@FeignClient("HYSTRIX") public interface HystrixClient { @GetMapping("/demo") String demo(@RequestParam("id") Integer id); }
-
测试
正常访问
如果9998服务(HYSTRIX)服务熔断,我们的调用结果是:
如果9998服务(HYSTRIX)服务完全不可用,我们的调用结果是:
如此调用会返回错误页面,应该在客户端也实现快速失败方法。
-
在配置文件中,开启客户端OpenFeign在调用过程中,开启Hystrix支持
# 开启客户端OpenFeign在调用过程中,开启Hystrix支持 feign.hystrix.enabled=true
-
在Feign接口上传入一个实现HystrixClient接口的类
@FeignClient(value = "HYSTRIX", fallback = HystrixClientFallBack.class) // fallback: 用来指定当前服务不可用时,默认的备选处理方法(类),需要传入一个类 public interface HystrixClient { @GetMapping("/demo") String demo(@RequestParam("id") Integer id); }
-
创建
HystrixClientFallBack
,并实现HystrixClient
接口,接口中的每个方法的实现,就是每个调用的fallback默认的快速失败方法@Component //@Configuration // 也可,相当于Spring中的XML public class HystrixClientFallBack implements HystrixClient { @Override public String demo(Integer id) { return "当前服务不可用,稍后再试! id = " + id; } }
-
测试:访问http://localhost:9999/demo
分析总结:
- 在我们的例子中,服务端不能接收小于0的id,假设客户端不断发送id小于0的请求给服务端,服务端可能就会熔断(这里模拟服务端自身因请求量过大或自身等等原因而短暂熔断),此时客户端再调用拿到的结果就是“服务端返回的快速失败的结果”;当我们关闭服务端(模拟服务端彻底宕机),此时客户端再调用拿到的结果就是“客户端定义的快速失败的结果”。
- 这么设计的好处是,客户端不会因为服务端宕机而自身服务调用失败(一开始会返回500页面,添加客户端的快速失败方法后依然可以正常运行)
2.3 Dashboard(弃用)
Hystrix Dashboard的一个主要优点是它收集了关于每个HystrixCommand的一组度量(监控每一个@HystrixCommand
注解创建一组度量)。Hystrix仪表板以高效的方式显示每个断路器的运行状况。本身仅仅是一个仪表盘,并且是一个独立应用(类似Eureka Server),不写任何业务代码,不会被调用,不强制注册到服务注册中心。
构建
-
创建项目
springcloud_10_hystrix_dashboard
-
引入依赖
<dependencies> <!--springboot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--consul: service registry--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!--actuator: 暴露各种指标,包括health check--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--hystrix dashboard--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> </dependencies>
-
配置文件(DashBoard可以不用注册到服务注册中心)
server.port=9099 spring.application.name=DASHBOARD # consul server 服务注册地址 spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 # 指定当前注册的服务的服务名,默认引用:spring.application.name spring.cloud.consul.discovery.service-name=${spring.application.name}
-
入口类
@SpringBootApplication @EnableHystrixDashboard // 开启当前应用为Hystrix仪表盘 public class DashboardApplication { public static void main(String[] args) { SpringApplication.run(DashboardApplication.class, args); } }
-
访问仪表盘web页面,端口号就是仪表盘服务的端口号:http://localhost:9099/hystrix
-
启动被监控服务:
springcloud_08_hystrix
,写入监控url: -
开启监控后发现一直loading…,说明没有读取到
-
解决方法:在微服务中加入一段配置:监控的项目中入口类中加入监控路径配置[新版本坑](或者单独放在configuration中,两者没有任何区别),并启动监控项目
@Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
-
加入后还是loading…,因为JQuery版本的问题
2.4 停止维护
# 官方地址:https://github.com/Netflix/Hystrix
- 翻译:Hystrix(版本1.5.18)足够稳定,可以满足Netflix对我们现有应用的需求。同时,我们的重点已经转移到对应用程序的实时性能作出反应的更具适应性的实现,而不是预先配置的设置(例如,通过自适应并发限制)。对于像Hystrix这样的东西有意义的情况,我们打算继续在现有的应用程序中使用Hystrix,并在新的内部项目中利用诸如resilience4j这样的开放和活跃的项目。我们开始建议其他人也这样做。 ----> sentinel 流量卫兵
- Dashboard也被废弃