SpringCloud之Hystrix【服务降级、服务熔断、服务限流】
Hystrix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布是系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的的线程不会被长时间、不必要的占用,从而避免对了故障在分布式系统中分蔓延、雪崩
1.服务降级
比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案
哪些情况可能出现降级:
- 程序运行异常
- 超时
- 服务熔断歘服务降级
- 线程池、信号量打满导致服务降级
先模拟场景
- 建服务提供者模块cloud-provider-hystrix-payment8001
- 导依赖
- 编写配置文件
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true # true向服务中心注册自己
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka # 注册地址和端口
instance:
instance-id: payment-hystrix-8001
prefer-ip-address: true # 访问路径可以显示IP地址
- 编写业务代码
创建两个方法,一个正常返回【paymentInfo_OK】,另一个休眠5
秒【paymentInfo_TimeOut】 - 编写启动类,加上
@EnableEurekaClient
注解,注册进Eureka注册中心 - 启动测试、压力测试,进行自测
发现低并发量时,方法的访问都正常。但是压测时,如果大流量请求休眠的方法时,访问正常方法的响应速度也会受影响,导致卡顿变慢
此时再建一个客户端模块cloud-consumer-feign-hystrix-order80,模拟在服务提供者自身承载着比较大的压力的情况下,此时消费者也来调用服务的场景
- 建模块
- 导依赖
Hystrix、OpenFeign - 写配置
server:
port: 80
eureka:
client:
register-with-eureka: false # true向服务中心注册自己
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka # 注册地址和端口
#记得配置这两个属性,否则会发生超时异常
ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000
- 写主启动类,并添加
@EnableFeignClients
注解 - 添加service接口
@Component
@FeignClient(value = "cloud-provider-hystrix-payment")
public interface PaymentHystrixService {
@GetMapping("payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("payment/hystrix/timeout/{id}")
public 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){
return paymentHystrixService.paymentInfo_OK(id);
}
@GetMapping("consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
return paymentHystrixService.paymentInfo_TimeOut(id);
}
}
- 启动压力测试,客户端80去访问压力较大的服务提供方8001
出现80客户端调用响应缓慢,甚至超时的情况
解决
- 超时导致服务器变慢(转圈) ———— 超时不再等待
- 出错(岩机或程序运行出错) ———— 出错要有兜底
对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)宕机了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)没问题,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者,自己处理降级)
服务提供端处理
- 在目标方法上加注解
@HystrixCommand
,启用服务降级,并添加降级方法paymentInfo_TimeOut_Handler
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOut_Handler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
fallbackMethod
表示降级的方法的方法名(自定义),commandProperties后面部分表示,设置自身调用超时时间的峰值为3秒,峰值内可以正常运行,超过了需要有兜底的方法处理,做服务降级fallback
2. 在主启动类上添加@EnableCircuitBreaker
注解,激活服务降级
3. 8001自测,是否因为休眠导致超时或者发生异常,就会服务降级,执行fallback方法。
服务消费端处理
- 增加yml配置项
feign:
hystrix:
enabled: true
- 主启动类上加
@EnableHystrix
注解 - 然后到方法的实现上加
@HystrixCommand
注解,并定义降级方法paymentTimeOutFallbackMethod
@GetMapping("consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
- 启动测试,分别测试超时和运行异常,是否都会执行服务降级方法
完善
上述的做法存在的问题:
- 一个方法就得对应一个降级方法,不可取,导致代码膨胀
- 降级方法的定义与业务逻辑代码糅杂在一起,耦合度高
- 《解决问题1》在方法实现的类上引入
@DefaultProperties(defaultFallback="方法名")
,这样,除了个别专门配置了降级方法的,其他只有@HystrixCommand
这一个注解(注解后没其他属性)标注的方法,都可以通过defaultFallback指定的方法来做统一的处理(服务提供端与服务消费方操作一样) - 《解决问题2》只需在Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。添加接口的一个实现类,实现对降级方法的集中定义,要保证yml文件中
feign.hystrix.enabled=true
,然后在接口
的@FeignClient的
注解中添加一个fallback属性的配置@FeignClient(value = “cloud-provider-hystrix-payment”, ·fallback =PaymentFallbackService.class
)。
注意:采用这种方式,就不需要在具体的目标方法上添加@HystrixCommand
注解了,如果加了,@HystrixCommand
的优先级更高一些。
2.服务熔断
当某个服务出现问题卡死了,不能让用户一直等待,需要关闭所有对此服务的访问,然后调用服务降级
- 在服务提供端的Service中,定义一个测试断路器的方法和降级方法
// 服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", 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 paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id不能为负数,请稍后再试,id为"+id;
}
属性配置在这个类中可以查看com.netflix.hystrix.HystrixCommandProperties
2. 在controller中调用
3. 测试:大量调用会产生异常的方法,马上请求调用正常的方法,会发现熔断器打开,没法调用正常的方法,但会慢慢恢复
总结
熔断开启的条件
- 当满足一定的阈值时,默认10秒内超过20个请求
- 当失败率达到一定值,默认10秒内超过50%的请求失败
一段时间后(默认5秒),这个时候熔断器是半开状态,会让其中一个请求进行转发,如果成功,断路器会关闭,如果失败,继续开启,重新计时。
熔断整体流程:
- 请求进来,首先查询缓存,如果缓存有,直接返回,如果缓存没有,进入下面步骤
- 查看断路器是否开启
- 如果开启的,Hystrix直接将请求转发到降级返回,然后返回
- 如果断路器是关闭的,判断线程池等资源是否已经满了
- 如果已经满了,也会走降级方法
- 如果资源没有满,判断我们使用的什么类型的Hystrix,决定调用构造方法还是run方法,然后处理请求,Hystrix会将本次请求的结果信息汇报给断路器,因为断路器此时可能是开启的(断路器开启也是可以接收请求的),断路器收到信息,判断是否符合开启或关闭断路器的条件
- 如果本次请求处理失败,又会进入降级方法
- 如果处理成功,判断处理是否超时
- 如果超时了,也进入降级方法
- 没有超时,则本次请求处理成功,将结果返回给controller
断路器逻辑
- 当断路器开启,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback,通过断路器,实现了自动地发现错误,并将降级逻辑切换为主逻辑,减少响应延迟的效果。
- 原来的主逻辑要如何恢复呢?
对于这一问题,hystrix也为我们实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,断路器关闭,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
3.服务限流
限流,比如秒杀场景,不能让用户瞬间都访问服务器,限制一次只可以有多少请求
后面学习sentinal时再总结
Hystrix Dashboard监控
- 建模块
- 导依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- 写配置
server:
port: 9001
- 新建主启动类,添加
@EnableHystrixDashboard
注解 - 启动,服务
http://localhost:9001/hystrix
此时要监控服务,还需要在在服务的主启动类中添加这么一个Bean的配置,才能监控到服务
(cloud-provider-hystrix-payment8001为例)
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream"
* 只要在自己的项目里配置上下面的servlet就可以了
* I
*/
@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;
}
填入监控服务的IP+端口/hystrix.stream
对cloud-provider-hystrix-payment8001
的访问都可以在Dashboard上监控到
END
本文地址:https://blog.csdn.net/K_kzj_K/article/details/107360512
下一篇: 域环境基础概念
推荐阅读
-
SpringCloud之服务注册与发现Spring Cloud Eureka实例代码
-
springcloud学习之路: (四) springcloud集成Hystrix服务保护
-
最基础springcloud微服务教学(六)--- Hystrix 断路器
-
SpringCloud之Eureka:服务发布与调用例子
-
SpringCloud-使用熔断器防止服务雪崩-Ribbon和Feign方式(附代码下载)
-
Spring Cloud第五篇 | 服务熔断Hystrix
-
Springcloud hystrix服务熔断和dashboard如何实现
-
Spring Cloud实战之初级入门(四)— 利用Hystrix实现服务熔断与服务监控
-
springcloud之服务注册与发现(zookeeper注册中心)-Finchley.SR2版
-
Hystrix关于服务降级