目录
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩:
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多系统资源,进而引起系统崩溃,引起所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这个模块依然还接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix的作用:服务降级、服务熔断、接近实时的监控。
1.服务降级:当程序运行异常、服务超时、服务熔断、线程池满了的时候会触发,返回给调用者“服务器忙,请稍后再试”等较友好的内容(不让客户端等待并立刻返回一个fallback)
2.服务熔断:类比保险丝,达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
一般的流程:服务降级->进而熔断->恢复调用链路
3.服务限流:秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
1.新建maven子工程:cloud-provider-hystrix-payment8001
2.POM
- <dependencies>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-actuatorartifactId>
- dependency>
- <dependency>
- <groupId>com.atguigu.springcloudgroupId>
- <artifactId>cloud-api-commonsartifactId>
- <version>${project.version}version>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-devtoolsartifactId>
- <scope>runtimescope>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- <scope>testscope>
- dependency>
- dependencies>
3.YML
- server:
- port: 8001
-
- spring:
- application:
- name: cloud-provider-hystrix-payment
-
- eureka:
- client:
- register-with-eureka: true
- fetch-registry: true
- service-url:
- #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
- defaultZone: http://eureka7001.com:7001/eureka
4.主启动
- @SpringBootApplication
- @EnableEurekaClient //本服务启动后会自动注册进eureka服务中
- public class PaymentHystrixMain8001
- {
- public static void main(String[] args)
- {
- SpringApplication.run(PaymentHystrixMain8001.class,args);
- }
- }
5.业务类
- @Service
- public class PaymentService
- {
- /**
- * 正常访问,一切OK
- * @param id
- * @return
- */
- public String paymentInfo_OK(Integer id)
- {
- return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
- }
-
- /**
- * 超时访问,演示降级
- * @param id
- * @return
- */
- public String paymentInfo_TimeOut(Integer id)
- {
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
- }
- }
- @RestController
- @Slf4j
- public class PaymentController
- {
- @Autowired
- private PaymentService paymentService;
-
- @Value("${server.port}")
- private String serverPort;
-
-
- @GetMapping("/payment/hystrix/ok/{id}")
- public String paymentInfo_OK(@PathVariable("id") Integer id)
- {
- String result = paymentService.paymentInfo_OK(id);
- log.info("****result: "+result);
- return result;
- }
-
- @GetMapping("/payment/hystrix/timeout/{id}")
- public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException
- {
- String result = paymentService.paymentInfo_TimeOut(id);
- log.info("****result: "+result);
- return result;
- }
- }
6.正常测试
输入:http://localhost:8001/payment/hystrix/ok/31
http://localhost:8001/payment/hystrix/timeout/31
输出:
- 线程池: http-nio-8001-exec-2 paymentInfo_OK,id: 31 O(∩_∩)O哈哈~
- 线程池: hystrix-PaymentService-1 id: 31 O(∩_∩)O哈哈~ 耗时(秒): 5
7.高并发测试
使用Jmeter工具演示高并发的请求,访问paymentInfo_TimeOut方法,然后再访问paymentInfo_OK方法。在高并发之前,paymentInfo_OK方法执行得非常快。然而在大量访问paymentInfo_TimeOut方法的情况下,大量资源被占用,从而导致paymentInfo_OK方法执行速度也降低了。
如果这时再加上一个外部的消费者来访问,消费者也只能干等。
1.新建maven子工程:cloud-consumer-feign-hystrix-order80
2.POM
- <dependencies>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
- dependency>
-
- <dependency>
- <groupId>com.atguigu.springcloudgroupId>
- <artifactId>cloud-api-commonsartifactId>
- <version>${project.version}version>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-webartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-actuatorartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-devtoolsartifactId>
- <scope>runtimescope>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- <scope>testscope>
- dependency>
- dependencies>
3.YML
- server:
- port: 80
-
- eureka:
- client:
- register-with-eureka: false
- service-url:
- defaultZone: http://eureka7001.com:7001/eureka/
4.主启动
- @SpringBootApplication
- @EnableFeignClients
- public class OrderHystrixMain80
- {
- public static void main(String[] args)
- {
- SpringApplication.run(OrderHystrixMain80.class,args);
- }
- }
5.业务类
- @Component
- @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
- public interface PaymentHystrixService
- {
- @GetMapping("/payment/hystrix/ok/{id}")
- String paymentInfo_OK(@PathVariable("id") Integer id);
-
- @GetMapping("/payment/hystrix/timeout/{id}")
- String paymentInfo_TimeOut(@PathVariable("id") Integer id);
- }
- @RestController
- @Slf4j
- public class OrderHystirxController
- {
- @Resource
- private PaymentHystrixService paymentHystrixService;
-
- @GetMapping("/consumer/payment/hystrix/ok/{id}")
- public String paymentInfo_OK(@PathVariable("id") Integer id)
- {
- String result = paymentHystrixService.paymentInfo_OK(id);
- return result;
- }
-
- @GetMapping("/consumer/payment/hystrix/timeout/{id}")
- public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
- {
- String result = paymentHystrixService.paymentInfo_TimeOut(id);
- return result;
- }
- }
6.正常测试
输入:http://localhost/consumer/payment/hystrix/ok/32
输出:
线程池: http-nio-8001-exec-4 paymentInfo_OK,id: 32 O(∩_∩)O哈哈~
7.高并发测试
还是使用高并发工具去访问8001,再让80端去访问8001,要么速度很慢,要么返回错误页面
8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8001,客户端访问响应缓慢。
如何解决?
超时导致服务器变慢(转圈) - 超时不再等待
出错(宕机或程序运行出错) - 出错要有兜底(fallback)
对消费者、生产者端都可配置。
对于生产者,设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
如何使用?
使用@HystrixCommand注解加在方法上
- @Service
- public class PaymentService
- {
- /**
- * 正常访问,一切OK
- * @param id
- * @return
- */
- public String paymentInfo_OK(Integer id)
- {
- return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
- }
-
- /**
- * 超时访问,演示降级
- * @param id
- * @return
- */
- @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
- @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
- })
- public String paymentInfo_TimeOut(Integer id)
- {
- int second = 5;
- try { TimeUnit.SECONDS.sleep(second); } catch (InterruptedException e) { e.printStackTrace(); }
- return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费秒: "+second;
- }
- public String paymentInfo_TimeOutHandler(Integer id){
- return "/(ㄒoㄒ)/调用支付接口超时或异常:\t"+ "\t当前线程池名字" + Thread.currentThread().getName();
- }
- }
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
另外,要在主启动类上加注解:@EnableCircuitBreaker(开启断路器)
对于消费者,做以下修改:
1.YML添加下面属性:
- # 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
- feign:
- hystrix:
- enabled: true #在Feign中开启Hystrix
2.主启动类上加注解@EnableHystrix
3.修改controller
- @RestController
- @Slf4j
- public class PaymentHystirxController
- {
- @Resource
- private PaymentHystrixService paymentHystrixService;
-
- @GetMapping("/consumer/payment/hystrix/ok/{id}")
- public String paymentInfo_OK(@PathVariable("id") Integer id)
- {
- String result = paymentHystrixService.paymentInfo_OK(id);
- return result;
- }
-
- @GetMapping("/consumer/payment/hystrix/timeout/{id}")
- @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
- @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
- })
- public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
- {
- String result = paymentHystrixService.paymentInfo_TimeOut(id);
- return result;
- }
- public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
- {
- return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
- }
-
- }
如果为每个方法单独配一个fallback太麻烦了,我们可以指定统一的fallback。
我们可以在控制器上加注解@DefaultProperties(defaultFallback =" ")来指定默认fallback
例如:
可以看到当前fallback与业务方法都混在一块,我们希望把这些fallback统一提取出来,降低耦合
这里在消费者端进行演示,消费者端有PaymentHystrixService接口,我们重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理。当然也要在原来的service接口中加@FeignClient注解的属性fallback,声明fallback在哪里。
- @Component
- @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)
- 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);
- }
- @Component
- public class PaymentFallbackService implements PaymentHystrixService
- {
- @Override
- public String paymentInfo_OK(Integer id)
- {
- return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
- }
-
- @Override
- public String paymentInfo_TimeOut(Integer id)
- {
- return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
- }
- }
注意要在YML中开启服务降级。
全部配置完成后开始测试,把生产者down掉,再用消费者去访问,可以获得错误提示信息,而不是一直等待。
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
1.熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
2.熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断(一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启)
3.熔断关闭:熔断关闭不会对服务进行熔断(正常时的状态)
一旦进入熔断状态,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,
当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。
我们以生产者为例,修改其Paymentservice
附:所有的@HystrixCommand参数
- //=========服务熔断
- @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
- //开启断路器
- @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
- //请求次数超过峰值将关闭
- @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
- //请求时间范围
- @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
- //失败率阈值(本例中10s内请求次数达到10次,并且错误次数达到6次时启动熔断)
- @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 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
- }
修改Controller
- @GetMapping("/payment/circuit/{id}")
- public String paymentCircuitBreaker(@PathVariable("id") Integer id)
- {
- String result = paymentService.paymentCircuitBreaker(id);
- log.info("****result: "+result);
- return result;
- }
测试: http://localhost:8001/payment/circuit/31 http://localhost:8001/payment/circuit/-31
当输入的id大于0时,正常;当输入小于0时,fallback(服务降级)。当短时间内错误次数过多时启用熔断机制,服务直接不可用。过一会再输入大于0的id,服务恢复正常。
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
如何使用?
1.新建cloud-consumer-hystrix-dashboard9001
2.POM
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-actuatorartifactId>
- dependency>
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-devtoolsartifactId>
- <scope>runtimescope>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.projectlombokgroupId>
- <artifactId>lombokartifactId>
- <optional>trueoptional>
- dependency>
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-testartifactId>
- <scope>testscope>
- dependency>
- dependencies>
3,YML
- server:
- port: 9001
4.主启动,添加注解@EnableHystrixDashboard
- @SpringBootApplication
- @EnableHystrixDashboard
- public class HystrixDashboardMain9001
- {
- public static void main(String[] args)
- {
- SpringApplication.run(MainApp9001.class,args);
- }
- }
5.所有被监控对象要有这个依赖
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-actuatorartifactId>
- dependency>
6.假如我们要监控生产者-cloud-provider-hystrix-payment8001
新版本需要在8001的主启动类MainAppHystrix8001中指定监控路径:
- /**
- *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
- *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
- *只要在自己的项目里配置上下面的servlet就可以了
- */
- @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;
- }
然后访问监控:http://localhost:9001/hystrix
填写要监控的地址: http://localhost:8001/hystrix.stream
监控说明: