一、 什么是灾难性的雪崩效应
在微服务架构的项目中,尤其是中大型项目,肯定会出现一个服务调用其他的服务,其他服务又调用别的服务,服务和服务之间形成了一种链式的调用关系。
当少量请求时,对于整个服务链条是没有过多的影响的。
虽然每个服务的请求都是少量的,但是最终都访问服务T。所以对于服务T来说请求量就是比较大的。所在的服务器CPU压力比较高。
当其中某一个服务突然遇到大量请求时。整个链条上所有服务负载骤增。
导致服务U和服务T的负载过高。运行性能下降。会导致其他调用服务U和服务T的链条出现问题。从而所有的项目可能都出现的问题。
这种情况就称为灾难性的雪崩效应。
造成灾难性雪崩效应的原因,可以简单归结为下述三种:
服务提供者(Application Service)不可用。如:硬件故障、程序BUG、缓存击穿、并发请求量过大等。
重试加大流量。如:用户重试、代码重试逻辑等。
服务调用者(Application Client)不可用。如:同步请求阻塞造成的资源耗尽等。
雪崩效应最终的结果就是:服务链条中的某一个服务不可用,导致一系列的服务不可用,最终造成服务逻辑崩溃。这种问题造成的后果,往往是无法预料的。
配套视频:
Spring框架基础讲解_尚学堂spring框架核心教程_spring框架+实战练习 从入门到精通_哔哩哔哩 (゜-゜)つロ 干杯~-https://www.bilibili.com/video/BV1Y5411x7ce
配套资源:
网盘链接:https://pan.baidu.com/s/1DiZbVjzfjUZO9E6LDaU-4g
提取码:m7uz
一、 如何防止灾难性雪崩效应
降级
超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值.
保证:服务出现问题整个项目还可以继续运行。
熔断
当失败率(如因网络故障/超时造成的失败率高)达到阀值自动触发降级,熔断器触发的快速失败会进行快速恢复。
通俗理解:熔断就是具有特定条件的降级,当出现熔断时在设定的时间内容就不在请求Application Service了。所以在代码上熔断和降级都是一个注解
保证:服务出现问题整个项目还可以继续运行。
请求缓存
提供了请求缓存。服务A调用服务B,如果在A中添加请求缓存,第一次请求后走缓存了,就不在访问服务B了,即使出现大量请求时,也不会对B产生高负载。
请求缓存可以使用Spring Cache实现。
保证:减少对Application Service的调用。
请求合并
提供请求合并。当服务A调用服务B时,设定在5毫秒内所有请求合并到一起,对于服务B的负载就会大大减少,解决了对于服务B负载激增的问题。
保证:减少对Application Service的调用。
隔离
隔离分为线程池隔离和信号量隔离。通过判断线程池或信号量是否已满,超出容量的请求直接降级,从而达到限流的作用。
三、 Hystrix简介
在Spring Cloud中解决灾难性雪崩效应就是通过Spring Cloud Netflix Hystrix实现的。
Hystrix [hɪst'rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix(中文:断路器)是Netflix开源的一款容错框架,同样具有自我保护能力。
通俗解释:Hystrix就是保证在高并发下即使出现问题也可以保证程序继续运行的一系列方案。作用包含两点:容错和限流。
在Spring cloud中处理服务雪崩效应,都需要依赖hystrix组件。在Application Client应用的pom文件中都需要引入下述依赖:
四、 降级
降级是指,当请求超时、资源不足等情况发生时进行服务降级处理,不调用真实服务逻辑,而是使用快速失败(fallback)方式直接返回一个托底数据,保证服务链条的完整,避免服务雪崩。
解决服务雪崩效应,都是避免application client请求application service时,出现服务调用错误或网络问题。处理手法都是在application client中实现。我们需要在application client相关工程中导入hystrix依赖信息。并在对应的启动类上增加新的注解@EnableCircuitBreaker,这个注解是用于开启hystrix熔断器的,简言之,就是让代码中的hystrix相关注解生效。
1 新建ApplicationServiceDemo
1.1 配置pom.xml
1.2 新建配置文件
新建application.yml
1.3 新建控制器
新建com.bjsxt.controller.DemoController
1.4 新建启动类
新建com.bjsxt.DemoApplication
2 新建DemoFallback
新建项目DemoFallback作为Application Client
2.1 编写pom.xml
2.2 新建配置文件
新建application.yml
注意不要端口号冲突
2.3 新建配置类
新建com.bjsxt.config.RibbonConfig
此处使用@LoadBalancer方式快捷配置负载均衡。
2.4 新建service及实现类
新建com.bjsxt.service.DemoService及实现类。
实现类中@HystrixCommand的fallbackMethod配置的就是降级方法。
myFallback方法:
参数:和test的参数是相同的。
返回值:和test中调用的application-service-demo/demo控制器方法返回值相同
2.5 新建控制器
新建com.bjsxt.controller.FallbackController
2.6 新建启动类
2.7 访问
在浏览器输入http://localhost:8081/demo
3 测试降级
停止ApplicationServiceDemo项目。再次访问
五、 熔断
当一定时间内,异常请求比例(请求超时、网络故障、服务异常等)达到阀值时,启动熔断器,熔断器一旦启动,则会停止调用具体服务逻辑,通过fallback快速返回托底数据,保证服务链的完整。
熔断有自动恢复机制,如:当熔断器启动后,每隔5秒,尝试将新的请求发送给Application Service,如果服务可正常执行并返回结果,则关闭熔断器,服务恢复。如果仍旧调用失败,则继续返回托底数据,熔断器持续开启状态。
降级是出错了返回托底数据,而熔断是出错后如果开启了熔断将会一定时间不在访问application service
1 属性
熔断的实现是在调用远程服务的方法上增加@HystrixCommand注解。当注解配置满足则开启或关闭熔断器。
@HystrixProperty的name属性取值可以使用HystrixPropertiesManager常量,也可以直接使用字符串进行操作。
注解属性描述:
CIRCUIT_BREAKER_ENABLED
"circuitBreaker.enabled";
是否开启熔断策略。默认值为true。
CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD
"circuitBreaker.requestVolumeThreshold";
单位时间内(默认10s内),请求超时数超出则触发熔断策略。默认值为20次请求数。通俗说明:单位时间内容要判断多少次请求。
EXECUTION_ISOLATION_THREAD_TIMEOUT_IN_MILLISECONDS
execution.isolation.thread.timeoutInMilliseconds
设置单位时间,判断circuitBreaker.requestVolumeThreshold的时间单位,默认10秒。单位毫秒。
CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS
"circuitBreaker.sleepWindowInMilliseconds";
当熔断策略开启后,延迟多久尝试再次请求远程服务。默认为5秒。单位毫秒。这5秒直接执行fallback方法,不在请求远程application service。
CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE
"circuitBreaker.errorThresholdPercentage";
单位时间内,出现错误的请求百分比达到限制,则触发熔断策略。默认为50%。
CIRCUIT_BREAKER_FORCE_OPEN
"circuitBreaker.forceOpen";
是否强制开启熔断策略。即所有请求都返回fallback托底数据。默认为false。
CIRCUIT_BREAKER_FORCE_CLOSED
"circuitBreaker.forceClosed";
是否强制关闭熔断策略。即所有请求一定调用远程服务。默认为false。
2 代码示例
在原有降级代码上修改@HystrixCommand如下。
关闭ApplicationSeviceDemo项目,访问DemoFallback控制器,刷新5次后会发现页面加载快了,这时就开启熔断了。此时打开ApplicationServiceDemo,发现依然返回托底数据。到达30秒后再次访问才能正常访问ApplicationServiceDemo中内容。
注意:单位时间时间内容请求数必须达到5个(无论成功还是失败)才能满足条件。
六、 请求缓存
Hystrix为了降低访问服务的频率,支持将一个请求与返回结果做缓存处理。如果再次请求的URL没有变化,那么Hystrix不会请求服务,而是直接从缓存中将结果返回。这样可以大大降低访问服务的压力。
Hystrix自带缓存。有两个缺点:
1. 是一个本地缓存。在集群情况下缓存是不能同步的。
2. 不支持第三方缓存容器。Redis,memcached不支持的。
所以可以利用spring cache。实现请求缓存。
在降级处理的代码基础上完成下面变化。
1 添加依赖
1 修改配置文件
添加redis的配置,此处使用的是redis单机版。如果是redis集群使用spring.redis.cluster.nodes进行配置。
spring:
redis: host: 192.168.192.128
2 修改启动类
在启动类上添加@EnableCaching注解
3 在HystrixService和实现类上添加
在service实现类方法上面额外在添加一个注解。
@Cacheable(key = "'client'",cacheNames = "com.bjsxt")
4 编写控制器,进行测试
控制器代码没有变化,直接调用service即可。
5 检查结果
通过在redis中keys * 查看是否添加/删除key成功。
七、 请求合并
没有请求合并
Application Service 负载是Application Client发送请求的总数量
请求合并
把一段时间范围内的所有请求合并为一个请求。大大的降低了Application Service 负载。
什么情况下使用请求合并
在微服务架构中,我们将一个项目拆分成很多个独立的项目,这些独立的项目通过远程调用来互相配合工作,但是,在高并发情况下,通信次数的增加会导致总的通信时间增加,同时,线程池的资源也是有限的,高并发环境会导致有大量的线程处于等待状态,进而导致响应延迟,为了解决这些问题,我们需要来了解Hystrix的请求合并。
请求合并的缺点
设置请求合并之后,本来一个请求可能5ms就搞定了,但是现在必须再等10ms看看还有没有其他的请求一起的,这样一个请求的耗时就从5ms增加到15ms了,不过,如果我们要发起的命令本身就是一个高延迟的命令,那么这个时候就可以使用请求合并了,因为这个时候时间窗的时间消耗就显得微不足道了,另外高并发也是请求合并的一个非常重要的场景。
1 请求合并参数介绍
请求合并理论学习明白后,代码实现起来还是很容易的。关键就是一个@HystrixCollapser注解
2 代码实现
实现请求合并时,不仅仅需要修改Application Client代码,还需要修改Application Service的代码,因为Application Service必须支持把所有参数捆绑到一起的方式,同时还支持把多个值一起返回。
2.1 修改Application Service控制器
没有请求合并时的控制器
有请求合并时必须要支持一次传递过来多个参数和一次返回多个值。在额外添加一个控制器方法。
参数添加@RequestBody是因为Application Client使用请求体传递数据。
返回值也是List,List里面数据是有顺序的。
2.2 修改Application Client的Service
在请求缓存的代码基础上,在service及实现类中添加新方法。
@HystrixCollapser 进行请求合并
batchMethod:处理请求合并的方法
scope - 合并请求的请求作用域。可选值有global和request。
global代表所有的请求线程都可以等待可合并。 常用
request代表一个请求线程中的多次远程服务调用可合
timerDelayInMilliseconds:等待时长,默认10毫秒。
maxRequestInBatch:最大请求合并数量。
@HystrixCommand 处理请求合并的方法必须有此注解。
实现类中client(String)方法一旦被@HystrixCollapser标记,方法就不会被执行,方法体中为空即可。直接执行batchMethod对应的方法。
batchMethod方法返回值顺序和传递进来的参数顺序有关系的。
注意:
在实际测试中scope使用默认值REQUEST会出现空指针异常,请换成GLOBAL
2.3 修改consumer中控制器方法
在一个控制器中多次调用service的方法进行模拟并发操作。
控制通过休眠模拟两次并发请求。
f1和f2的输出语句必须放到请求后面,否则无法合并。
八、 隔离
1 线程池隔离
1.1 为什么使用线程池隔离
没有线程池隔离的时候可能因为某个接口的高并发导致其他接口也可用。
当使用线程池隔离。不同接口有着自己独立的线程池
即使某个线程池都被占用,也不影响其他线程。
1.2 Hystrix的线程池隔离
Hystrix采用Bulkhead Partition舱壁隔离技术。
舱壁隔离指的的是讲船体内部分为多个隔舱,一旦其中某几个隔舱发生破损进水,水流不会在其他舱壁中流动,从而保证船舱依然具有足够的浮力和稳定性,降低沉船危险。
1.3 线程池隔离的优缺点
优点:
1. 任何一个服务都会被隔离在自己的线程池内,即使自己的线程池资源填满也不会影响其他服务。
2. 当依赖的服务重新恢复时,可通过清理线程池,瞬间恢复服务的调用。但是如果是tomcat线程池被填满,再恢复就会很麻烦。
3. 每个都是独立线程池。一定程度上解决了高并发问题。
4. 由于线程池中线程个数是有限制,所以也解决了限流问题。
缺点:
1. 增加了CPU开销。因为不仅仅有Tomcat的线程池,还需要有Hystrix线程池。
2. 每个操作都是独立的线程,就有排队、调度和上下文切换等问题。
1.4 代码演示
访问thread方法和访问thread2方法时发现已经不再使用同一个线程池了。
1.5 参数说明
2 信号量隔离
2.1 信号量是什么
java.util.concurrent.Semaphore用来控制可同时并发的线程数。通过构造方法指定内部虚拟许可的数量。每次线程执行操作时先通过acquire方法获得许可,执行完毕再通过release方法释放许可。如果无可用许可,那么acquire方法将一直阻塞,直到其它线程释放许可。
如果采用信号量隔离技术,每接收一个请求,都是服务自身线程去直接调用依赖服务,信号量就相当于一道关卡,每个线程通过关卡后,信号量数量减1,当为0时不再允许线程通过,而是直接执行fallback逻辑并返回,说白了仅仅做了一个限流。
2.2 代码实现
2.3 参数说明
3 线程池隔离和信号量隔离
4 限流说明
在高并发的系统中,往往需要在系统中做限流,一方面是为了防止大量的请求使服务器过载,导致服务不可用,另一方面是为了防止网络攻击。
通过Hystrix的线程池隔离和信号量隔离控制了线程数量也就实现了限流效果。
九、 Hystrix-dashboard
Hystrix-dashboard能够让Actuator从json转换为界面。
在包含Hystrix的项目中(DemoFallback)进行操作
1 添加依赖
2 修改启动器注解
@EnableHystrix
@EnableHystrixDashboard
3 修改配置文件
management:
endpoints: web: exposure: include: hystrix.stream
4 在浏览器访问
先访问非界面版,等待ping成功。必须访问的是具有熔断、降级、线程池隔离、信号量隔离等具有Hystrix的方法,才会返回对应结果。
地址栏输入:http://localhost:8081/actuator/hystrix.stream
访问界面版。
在地址栏输入:http://localhost:8081/hystrix访问界面版。在界面的输入框中输入监控中心地址http://localhost:8081/actuator/hystrix.stream 后点击“Monitor Stream”
跳转到结果页面。
只有包含@HystrixCommand的方法,被执行后就会显示对应的图片
十、 OpenFeign的降级处理
当使用OpenFeign调用远程服务超时会出现500错误。如果不希望出现500可以使用OpenFeign自带的Hystrix进行降级处理。
1 编写配置文件
默认情况下Feign的hystrix是不开启的,需要手动开启。其他方式和之前配置是相同的。
2 添加依赖
Feign中包含Hystrix中部分功能。所以不需要单独导入Hystrix的依赖。
3 编写service
编写HystrixService内容。依然使用Feign进行声明式调用。
在案例中是直接使用内部类进行实现的,也可以新建一个类进行完成。
fallback属性表示降级后处理类。
4 编写启动器
十一、 OpenFeign的熔断处理
1 配置文件
在配置文件application.yml中增加下述配置,其他代码逻辑和配置内容与降级处理完全相同。
十二、 OpenFeign中的HystrixDashboard约束
spring-cloud-starter-openfeign中内嵌Hystrix相关jar包,但是在启用监控服务的时候,会有部分类型缺失,这是因为openfeign启动器中包含的Hystrix资源不完整。如果需要开启监控服务,必须单独导入Hystrix启动器资源spring-cloud-starter-netflix-hystrix。
原作者:北京尚学堂
原出处:知乎
原文链接:Hystrix断路器 如何使用 - 知乎