Hystrix
服务熔断
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免失败!
什么是服务熔断
我们可以把熔断器想象为一个保险丝,在电路系统中,一般在所有的家电系统连接外部供电的线路中间都会加一个保险丝,当外部电压过高,达到保险丝的熔点时候,保险丝就会被熔断,从而可以切断家电系统与外部电路的联通,进而保障家电系统不会因为电压过高而损坏
熔断机制是对应雪崩效应的一种微服务链路保护机制。
在分布式系统架构中多个系统之间通常是通过远程RPC调用进行通信,也就是 A 系统调用 B 系统服务,B 系统调用 C 系统的服务。当尾部应用 C 发生故障而系统 B 没有服务降级时候可能会导致 B,甚至系统 A 瘫痪,这种现象被称为雪崩现象。所以在系统设计时候要使用一定的降级策略,来保证当服务提供方服务不可用时候,服务调用方可以切换到降级后的策略进行执行
服务熔断解决如下问题:
- 当所依赖的对象不稳定时,能够起到快速失败的目的;
- 快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复
熔断机制
Hystrix提供的熔断器就有类似功能,当在一定时间段内服务调用方调用服务提供方的服务的次数达到设定的阈值,并且出错的次数也达到设置的出错阈值,就会进行服务降级 ,让服务调用方之间执行本地设置的降级策略,而不再发起远程调用。但是Hystrix提供的熔断器具有自我反馈,自我恢复的功能,Hystrix会根据调用接口的情况,让熔断器在closed,open,half-open三种状态之间自动切换。
- open状态说明打开熔断,也就是服务调用方执行本地降级策略,不进行远程调用。
- closed状态说明关闭了熔断,这时候服务调用方直接发起远程调用。
- half-open状态,则是一个中间状态,当熔断器处于这种状态时候,直接发起远程调用。
三种状态的转换:
- closed->open:正常情况下熔断器为closed状态,当访问同一个接口次数超过设定阈值并且错误比例超过设置错误阈值时候,就会打开熔断机制,这时候熔断器状态从closed->open。
- open->half-open:当服务接口对应的熔断器状态为open状态时候,所有服务调用方调用该服务方法时候都是执行本地降级方法,那么什么时候才会恢复到远程调用那?Hystrix提供了一种测试策略,也就是设置了一个时间窗口,从熔断器状态变为open状态开始的一个时间窗口内,调用该服务接口时候都委托服务降级方法进行执行。如果时间超过了时间窗口,则把熔断状态从open->half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用还是失败,则重新设置熔断器状态为open状态,从新记录时间窗口开始时间。
- half-open->closed: 当熔断器状态为half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用成功,则重新设置熔断器状态为closed状态。
那么有一个问题,用来判断熔断器从closed->open转换的数据是哪里来的那?其实这个是HystrixCommandMetrics对象来做的,该对象用来存在HystrixCommand的一些指标数据,比如接口调用次数,调用接口失败的次数等等
代码实例
首先我们用写一个SpringCloud-Provider-Dept-Hystrix 服务让他去调用SpringCloud-Provider-Dept-8001 这个微服务
依赖
<dependency>
<groupId>org.zheng</groupId>
<artifactId>SpringCloud-Api</artifactId>
<version>1.0-SNAPSHOT</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-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
application
server:
port: 8004
spring:
application:
name: provider-dept-hystrix
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: dept-server-api-hystrix(8004)
hostname: localhost
prefer-ip-address: true
info:
app.name: zheng
company.name: YouyouXin
注册bean
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller
@RestController
public class DeptController {
private final String REST_URL_PREFIX = "http://provider-dept";
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/dept/list")
public ArrayList<Dept> select(){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",ArrayList.class);
}
@RequestMapping("/consumer/dept/add")
public boolean addDept(Dept dept){
HashMap<String, Object> map = new HashMap<>();
System.out.println(dept);
map.put("deptName",dept.getDeptName());
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/add?deptName={deptName}",Boolean.class,map);
}
@RequestMapping("/consumer/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id")int id){
Dept dept = restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
if(dept==null){
throw new RuntimeException("数据库不存在该数据!");
}
return dept;
}
public Dept hystrixGet(@PathVariable("id")int id){
return new Dept().setDeptId(id).setDeptName("ID"+id+"未找到,该服务已由Hystrix熔断!").setDeptDatabase("未找到该数据库!");
}
}
主启动类
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "provider-dept",configuration = ZoneAvoidanceRule.class)
@EnableHystrix
public class SpringCloud_Dept_Hystrix {
public static void main(String[] args) {
SpringApplication.run(SpringCloud_Dept_Hystrix.class,args);
}
}
在上面我们可以看到,我们只需要导入pom依赖后在主启动类上添加@EnableHystrix ,在我们需要的服务上面(通常是Controller Api)上添加@HystrixCommand(fallbackMethod = "hystrixGet") 再编写指定的方法即可,但在这里我们需要注意的是,我们的参数,返回类型,需要与我们的被服务熔断的服务相匹配!
服务降级
什么是服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行
当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,我们可以将一些 不重要 或 不紧急 的服务或任务进行服务的 延迟使用 或 暂停使用
目的:降低服务粒度,要考虑整体模块粒度的大小,将粒度控制在合适的范围内
服务降级解决策略
消息中间件:所有API调用可以使用消息中间件进行控制
前端页面:指定网址不可访问(NGINX+LUA)
底层数据驱动:拒绝所有增删改动作,只允许查询
服务降级方式
-
**服务接口拒绝服务:**无用户特定信息,页面能访问,但是添加删除提示服务器繁忙。页面内容也可在Varnish或CDN内获取。 -
**页面拒绝服务:**页面提示由于服务繁忙此服务暂停。跳转到varnish或nginx的一个静态页面。 -
**延迟持久化:**页面访问照常,但是涉及记录变更,会提示稍晚能看到结果,将数据记录到异步队列或log,服务恢复后执行。 -
随机拒绝服务:服务接口随机拒绝服务,让用户重试,目前较少有人采用。因为用户体验不佳。
持久层降级方式
数据操作动作 | 通过Cache工作 | 通过异步数据队列 |
---|
增insert | 禁止 | 允许但不能有重复问题 | 删delete | 禁止 | 允许但不能有复合操作 | 改update | 禁止 | 允许只留最后结果 | 查query | 允许,若未命中问询mysql或其他持久层 | 走cache |
降级方式
直觉管理方式:运维人员可以指定哪些模块降级。
-
当服务器检测到压力增大,服务器监测自动发送通知给运维人员 -
运维人员根据自己或相关人员判断后通过配置平台设置当前运行等级来降级 -
降级首先可以对非核心业务进行接口降级。 -
如果效果不显著,开始对一些页面进行降级,以此保证核心功能的正常运行。
分级管理方式:运维人员无需关心业务细节,直接按级别降低即可。
具体实现
分级降级
当微服务架构发生不同程度的情况时,我们可以根据服务的对比而进行选择式舍弃(即丢车保帅的原则),从而进一步保障核心的服务的正常运作。
如果等线上服务即将发生故障时,才去逐个选择哪些服务该降级、哪些服务不能降级,然而线上有成百上千个服务,则肯定是来不及降级就会被拖垮。同时,在大促或秒杀等活动前才去梳理,也是会有不少的工作量,因此==建议在开发期就需要架构师或核心开发人员来提前梳理好==,是否能降级的初始评估值,即是否能降级的默认值。
为了便于批量操作微服务架构中服务的降级,我们可以从全局的角度来建立服务重要程度的评估模型,如果有条件的话,建议可以使用 层次分析法(The analytic hierarchy process,简称AHP) 的数学建模模型(或其它模型)来进行定性和定量的评估(肯定比架构师直接拍脑袋决定是否降级好很多倍,当然难度和复杂度也会高许多,即你需要一个会数学建模人才),而层次分析法的基本思路是人对一个复杂的决策问题的思维和判断过程大体上是一样的
我们利用数学建模的方式或架构师直接拍脑袋的方式,结合服务能否降级的优先原则,并根据台风预警(都属于风暴预警)的等级进行参考设计,可将微服务架构的所有服务进行故障风暴等级划分为以下四种:
评估模型:
- 蓝色风暴 —— 表示需要小规模降级非核心服务
- 黄色风暴 —— 表示需要中等规模降级非核心服务
- 橙色风暴 —— 表示需要大规模降级非核心服务
- 红色风暴 —— 表示必须降级所有非核心服务
设计说明:
- 故障严重程度为:蓝色<黄色<橙色<红色
- 建议根据二八原则可以将服务划分为:80%的非核心服务+20%的核心服务
以上模型只是整体微服务架构的服务降级评估模型,具体大促或秒杀活动时,建议以具体主题为中心进行建立(不同主题的活动,因其依赖的服务不同,而使用不同的进行降级更为合理)。当然模型可以使用同一个,但其数据需要有所差异。最好能建立一套模型库,然后实施时只需要输入相关服务即可输出最终降级方案,即输出本次大促或秒杀时,当发生蓝色风暴时需要降级的服务清单、当发生黄色风暴时需要降级的服务清单……
降级权值
微服务架构中有服务权值的概念,主要用于负载时的权重选择,同样服务降级权值也是类似,主要用于服务降级选择时的细粒度优先级抉择。所有的服务直接使用以上简单的四级划分方式进行统一处理,显然粒度太粗,或者说出于同一级的多个服务需要降级时的 降级顺序 该如何?甚至我想要人工智能化的 自动降级,又该如何更细粒度的控制?
基于上述的这些AI化的需求,我们可以为每一个服务分配一个降级权值,从而便于更加智能化的实现服务治理。而其评估的数值,同样也可以使用数学模型的方式进行 定性 与 定量 的评估出来,也可以架构师根据经验直接拍脑袋来确定
代码实例
服务熔断主要是从单个微服务的进行的配置,就是说我们要对服务2进行服务熔断的配置
而我们的服务降级,是需要在消费者端进行服务降级的配置
消费者端主要由Ribbon或Feign组成
对于Ribbon
就是给我们的服务的每一个API接口写上回调函数罢了!
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主启动类,开启@EnableHystrix
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
@RibbonClient(name = "provider-dept",configuration = ZoneAvoidanceRule.class)
public class SpringCloudHystrixRibbonHystrix {
public static void main(String[] args) {
SpringApplication.run(SpringCloudHystrixRibbonHystrix.class,args);
}
}
给每一个方法写上回调函数@HystrixCommand 即可
和上面的服务熔断的写法是一样的!
@RestController
public class DeptConsumerDeptController {
private final String REST_URL_PREFIX = "http://provider-dept";
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/dept/list")
@HystrixCommand(fallbackMethod = "hystrixSelect")
public ArrayList<Dept> select(){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",ArrayList.class);
}
@RequestMapping("/consumer/dept/add")
@HystrixCommand(fallbackMethod = "hystrixAddDept")
public boolean addDept(Dept dept){
HashMap<String, Object> map = new HashMap<>(2);
System.out.println(dept);
map.put("deptName",dept.getDeptName());
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/add?deptName={deptName}",Boolean.class,map);
}
@RequestMapping("/consumer/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id")int id){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
}
public boolean hystrixAddDept(Dept dept) {
return false;
}
public Dept hystrixGet(int id) {
return new Dept().setDeptId(-1).setDeptName("服务器繁忙,请稍候重试!").setDeptDatabase("数据库繁忙!");
}
public ArrayList<Dept> hystrixSelect() {
ArrayList<Dept> list = new ArrayList<>();
list.add(new Dept().setDeptId(-1).setDeptName("服务器繁忙,请稍候重试!").setDeptDatabase("数据库繁忙!"));
return list;
}
}
对于Feign
Feign其实和Ribbon差不多,主要不同是Feign需要手动开启!
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
开启Hystrix
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = "com.zheng.springcloud.service")
@EnableHystrix
public class SpringCloudFeignHystrix {
public static void main(String[] args) {
SpringApplication.run(SpringCloudFeignHystrix.class,args);
}
}
application中开启
feign:
hystrix:
enabled: true
在Api(SpringCloud-Ap i)中,配置回调函数,当然也要导入依赖
问题来了,我们在接口中没有办法去写回调函数的方法啊,怎么办?我们可以做如下的代码
自定义一个类接口FallbackFactory
这里我们必须@Component 让我们的Spring托管
那么就有小伙伴要问你,为什么这里需要@Component 托管而在我们之前的自定义负载均衡策略的时候就不需要进行@Component 托管呢?
我们查看@RibbonClient(name = “provider-dept”,configuration = ZoneAvoidanceRule.class)的源码可以看到
@Configuration(proxyBeanMethods = false)
@Import(RibbonClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RibbonClient {
String value() default "";
String name() default "";
Class<?>[] configuration() default {};
}
有个@Import(RibbonClientConfigurationRegistrar.class) 来进行自动托管,具体的@import的作用可以查看我的SpringCloud自动装配原理的文章!
@Component
public class DeptFallback implements FallbackFactory {
@Override
public Object create(Throwable throwable) {
return new DeptService() {
@Override
public boolean addDept(Dept dept) {
return false;
}
@Override
public Dept get(int id) {
return new Dept().setDeptId(-1).setDeptName("服务器繁忙,请稍候重试!").setDeptDatabase("数据库繁忙!");
}
@Override
public ArrayList<Dept> select() {
ArrayList<Dept> list = new ArrayList<>();
list.add(new Dept().setDeptId(-1).setDeptName("服务器繁忙,请稍候重试!").setDeptDatabase("数据库繁忙!"));
return list;
}
};
}
}
然后在@FeignClient 中配置fallbackFactory
@FeignClient(value = "provider-dept",fallbackFactory = DeptFallback.class)
public interface DeptService {
@RequestMapping("/dept/add")
public boolean addDept(Dept dept);
@RequestMapping("/dept/get/{id}")
public Dept get(@PathVariable("id")int id);
@RequestMapping("/dept/list")
public ArrayList<Dept> select();
}
这样就可以测试了!
服务降级和服务熔断对比
可参考一下的文章
-
阿里云:https://developer.aliyun.com/article/313046 -
csdn:https://blog.csdn.net/ityouknow/article/details/81230412 -
腾讯云:https://cloud.tencent.com/developer/article/1457494 -
知乎:https://zhuanlan.zhihu.com/p/341939685
==服务熔断==是应对系统服务雪崩的一种保险措施,给出的一种特殊降级措施。
==服务降级==是对系统整体资源的合理分配。区分核心服务和非核心服务。对某个服务的访问延迟时间、异常等情况做出预估并给出兜底方法。这是一种全局性的考量,对系统整体负荷进行管理。是更加宽泛的概念,主要是对系统整体资源的合理分配以应对压力。
服务熔断是服务降级的一种特殊情况,他是防止服务雪崩而采取的措施。系统发生异常或者延迟或者流量太大,都会触发该服务的服务熔断措施,链路熔断,返回兜底方法。这是对局部的一种保险措施。
具体点就是
-
服务熔断由链路上某个服务引起的,服务降级是从整体的负载考虑 -
服务熔断是一个框架层次的处理,服务降级是业务层次的处理 -
服务熔断一般是自我熔断恢复(被动),服务降级相当于人工控制(主动) -
服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从(整个业务所有服务 )整体负荷考虑
文章内容来源于,狂神的B站学习视频结合网络上各个博主的博客,结合个人理解总结而来
-
阿里云:https://developer.aliyun.com/article/313046 -
csdn:https://blog.csdn.net/ityouknow/article/details/81230412 -
腾讯云:https://cloud.tencent.com/developer/article/1457494 -
知乎:https://zhuanlan.zhihu.com/p/341939685 -
狂神主页:https://space.bilibili.com/95256449/
|