服务雪崩:多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其余的微服务,这就是所谓的“扇出”。若是扇出的链路上某个微服务的调用响应时间过长或不可用,对微服务A的调用就会占用愈来愈多的系统资源,进行引发系统崩溃。html
对于高流量的应用来讲,单一的后端依赖可能会致使全部服务器上的全部资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能致使服务之间的延迟增长,备份队列,线程和其余系统资源紧张,致使整个系统发生更多的级联故障。java
这些都表示须要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。git
官网: https://github.com/Netflix/Hystrix/wikigithub
目前已经进入维护模式。web
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖必不可免的会调用失败,好比超时、异常等,Hystrix可以保证在一个依赖出问题的状况下,不会致使总体服务失败,避免级联鼓掌,以提升分布式系统的弹性。spring
断路器自己是一种开关装置,当某个服务单元发生故障以后,经过断路器的故障监控(相似熔断保险丝)向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是常见的等待或者抛出调用方没法处理的异常,这样就保证了服务调用方的线程不会被长时间、没必要要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。后端
当服务器压力剧增的状况下,根据实际业务状况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运做或高效运做。缓存
服务熔断的做用相似于咱们家用的保险丝,当某服务出现不可用或响应超时的状况时,为了防止整个系统出现雪崩,暂时中止对该服务的调用。springboot
限流的目的是为了保护系统不被大量请求冲垮,经过限制请求的速度来保护系统。服务器
咱们先准备环境,准备一个正常的接口方法和一个模拟延迟五秒的接口方法。
spring-cloud-starter-netflix-hystrix
依赖引入。
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!--eureka client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
server: port: 8001 spring: application: name: cloud-provider-hystrix-payment # 服务名称! eureka: client: #表示是否将本身注册进EurekaServer默认为true。 register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: # #单机版 defaultZone: http://localhost:7001/eureka
@SpringBootApplication @EnableEurekaClient public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
@Service public class PaymentService { // 正常访问 public String paymentInfoOk(Integer id) { return "线程池: " + Thread.currentThread().getName() + " paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O哈哈~"; } // 延迟3秒 public String paymentInfoTimeOut(Integer id) { //int age = 10/0; try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时(秒): "; } }
@RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @GetMapping("/payment/hystrix/ok/{id}") public String paymentInfoOk(@PathVariable("id") Integer id) { String result = paymentService.paymentInfoOk(id); log.info("*****result: " + result); return result; } @GetMapping("/payment/hystrix/timeout/{id}") public String paymentInfoTimeOut(@PathVariable("id") Integer id) { String result = paymentService.paymentInfoTimeOut(id); log.info("*****result: " + result); return result; } }
依次启动:7001Eureka服务中心,8001服务,分别访问如下两个连接:
当并发请求量足够大的时候,微服务会集中资源去处理响应较慢的服务,致使其余原本响应较快的服务被拖累。Tomcat默认的工做线程被打满,没有多余的线程来分解压力和处理。
若是新建消费者客户端80,8001同一层次的其余接口服务被困死,80此时调用8001,客户端的响应也会变得很慢。
cloud-provider-hystrix-payment8001:在paymentInfoTimeOut方法上使用@HystrixCommand,并定义fallbackMethod方法。
@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") }) public String paymentInfoTimeOut(Integer id) { //int age = 10/0; try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时(秒): "; } public String paymentInfoTimeOutHandler(Integer id) { return "线程池: " + Thread.currentThread().getName() + " 8001系统繁忙或者运行报错,请稍后再试,id: " + id + "\t" + "o(╥﹏╥)o"; }
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中指定的指定方法。
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
设置了服务自身的上线时间为3s,超过3s将会调用paymentInfoTimeOutHandler方法。
主启动类加上激活注解:
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
访问:http://localhost:8001/payment/hystrix/timeout/1
接口进行测试,3s以后页面返回fallbackMethod设置方法的结果。既然超时情况能够进行兜底处理,那异常状况呢?
咱们不妨打开int age = 10/0;
的注释,此时会抛出一个算数异常,进行测试,一旦抛出异常,也会fallback。
cloud-consumer-feign-hystrix-order80:为消费者端也配置服务降级。首先配置yml,开启feign.hystrix.enabled
。
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ feign: hystrix: enabled: true
使用@HystrixCommand注解设置服务降级处理方法,和等待上限时间属性。
@RestController @Slf4j public class OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) public String paymentInfoTimeOut(@PathVariable("id") Integer id) { //int age = 10 / 0; return paymentHystrixService.paymentInfTimeOut(id); } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者本身运行出错请检查本身,o(╥﹏╥)o"; } }
在主启动类上加上注解:
@SpringBootApplication @EnableFeignClients @EnableHystrix public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class, args); } }
这时候的情景是:
咱们应当按照全局异常处理的思想,定义一套全局通用的处理降级方法,再此基础之上,再分别针对不一样的业务,进行定制降级。
@RestController @Slf4j @DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod") public class OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService; @HystrixCommand @GetMapping("/consumer/payment/hystrix/timeout/{id}") public String paymentInfoTimeOut(@PathVariable("id") Integer id) { //int age = 10 / 0; return paymentHystrixService.paymentInfTimeOut(id); } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者本身运行出错请检查本身,o(╥﹏╥)o"; } /** * 下面是全局fallback方法 */ public String paymentGlobalFallbackMethod() { return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~"; } }
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
。定义默认的fallback处理方法。The
@HystrixCommand
is provided by a Netflix contrib library called “javanica”. Spring Cloud automatically wraps Spring beans with that annotation in a proxy that is connected to the Hystrix circuit breaker. The circuit breaker calculates when to open and close the circuit and what to do in case of a failure.To configure the
@HystrixCommand
you can use thecommandProperties
attribute with a list of@HystrixProperty
annotations.
咱们首先须要知道,咱们全部须要调用服务端的接口的方法,其实都定义在FeignClient中。那么,咱们能够@FeignClient注解中经过fallback指定同一处理降级的服务:PaymentFallbackService
,咱们让这个service实现FeignClient,针对每一个方法提供不一样的降级策略。
首先确保开启:
feign: hystrix: enabled: true
@Component @FeignClient(value = "cloud-provider-hystrix-payment",fallback = PaymentFallbackService.class) public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") String paymentInfOk(@PathVariable("id") Integer id); @GetMapping("/payment/hystrix/timeout/{id}") String paymentInfTimeOut(@PathVariable("id") Integer id); }
@Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String paymentInfOk(Integer id) { return "PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o"; } @Override public String paymentInfTimeOut(Integer id) { return "PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o"; } }
http://localhost/consumer/payment/hystrix/ok/1
结果正常。熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错或不可用或响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
SpringCloud中能够经过Hystrix实现熔断,Hystrix会监控微服务之间调用的情况,当失败的调用到必定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
在Service层配置熔断策略:
//=====服务熔断 10s以内 10次请求有6次失败 就会开启断路器 @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(Integer id) { if (id < 0) { throw new RuntimeException("******id 不能负数"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber; }
@HystrixProperty中的配置项定义在:
com.netflix.hystrix.HystrixCommandProperties
更加详细的配置介绍:https://github.com/Netflix/Hystrix/wiki/Configuration
在Controller层调用:
//====服务熔断 @GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id) { String result = paymentService.paymentCircuitBreaker(id); log.info("****result: " + result); return result; }
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
断路器打开以后,再有请求调用的时候,将不会再调用主逻辑,而是直接调用降级fallback,经过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减小响应延迟的效果。
原来的逻辑如何恢复?
当断路器打开,对主逻辑进行熔断以后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,若是这次请求正常返回,那么断路器将闭合,主逻辑恢复,若是此次请求依然有问题,断路器继续进入打开状态,休眠时间窗从新计时。
构建 HystrixCommand【依赖的服务返回单个的结果】 or HystrixObservableCommand【依赖的服务返回多个操做结果】对象【1】。
执行命令,如下四种方法中的一种【前两种方法仅适用于简单的HystrixCommand对象,而不适用于HystrixObservableCommand】【2】:
K value = command.execute(); Future<K> fValue = command.queue(); Observable<K> ohValue = command.observe(); //hot observable Observable<K> ocValue = command.toObservable(); //cold observable
检查缓存【3】,若当前命令的请求缓存功能是被启用的,而且该命令缓存命中,那么这个缓存的响应将当即以Observable形式返回。
检查断路器是不是打开状态【4】,若是断路器打开,则Hystrix不会执行命令,而是处理fallback逻辑【8】,不然检查是否有可用资源来执行命令【5】。
线程池/请求队列/信号量是否已满【5】,若是命令依赖服务的专有线程池和请求队列,或信号量已经被占满,那么Hystrix也不会执行命令,而是转到第【8】步。
Hystrix会根据咱们编写的方法来决定采起什么样的方式去请求依赖服务【6】。
Hystrix向断路器报告成功、失败、拒绝和超时等信息,断路器经过维护一组计数器来统计这些数据,经过这些数据来决定是否要打开断路器【7】。
当命令执行失败时,Hystrix会进入fallback尝试回退处理,咱们一般称为:服务降级。可以引发服务降级处理的状况:
当Hystrix命令执行成功以后,它会将处理结果直接返回或是以Observable的形式返回。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
server: port: 9001
@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class, args); } }
访问:http://localhost:9001/hystrix
,出现如下界面说明配置成功:
保证8001的依赖中存在spring-boot-starter-actuator
。
<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>
特殊配置
/** * 此配置是为了服务监控而配置,与服务容错自己无关,spring cloud升级后的坑 * ServletRegistrationBean由于springboot的默认路径不是"/hystrix.stream", * 只要在本身的项目里配置上下面的servlet就能够了 */ @Bean public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); //访问路径 registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
这时,在Hystrix界面监控框输入监控的url:http://localhost:8001/hystrix.stream
,可能会出现错误:
Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names. If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList.
提醒咱们设置hystrix.dashboard.proxyStreamAllowList
,在yml中设置呗:
server: port: 9001 hystrix: dashboard: proxy-stream-allow-list: - "localhost"
启动8001,按照咱们测试服务熔断的流程,监控正常运行的效果:
本系列文章为《尚硅谷SpringCloud教程》的学习笔记【版本稍微有些不一样,后续遇到bug再作相关说明】,主要作一个长期的记录,为之后学习的同窗提供示例,代码同步更新到Gitee:https://gitee.com/tqbx/spring-cloud-learning,而且以标签的形式详细区分每一个步骤,这个系列文章也会同步更新。