赞
踩
分布式系统环境下,服务间类似依赖非常常见,一个业务调用通常依赖多个基础服务。如下图,对于同步调用,当库存服务不可用时,商品服务请求线程被阻塞,当有大批量请求调用库存服务时,最终可能导致整个商品服务资源耗尽,无法继续对外提供服务。并且这种不可用可能沿请求调用链向上传递,这种现象被称为雪崩效应。
在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程。
如存在如下调用链路:
而此时,Service A的流量波动很大,流量经常会突然性增加!那么在这种情况下,就算Service A能扛得住请求,Service B和Service C未必能扛得住这突发的请求。此时,如果Service C因为抗不住请求,变得不可用。那么Service B的请求也会阻塞,慢慢耗尽Service B的线程资源,Service B就会变得不可用。紧接着,Service A也会不可用,这一过程如下图所示
针对造成雪崩效应的不同场景,可以使用不同的应对策略,没有一种通用所有场景的策略,参考如下:
综上所述,如果一个应用不能对来自依赖的故障进行隔离,那该应用本身就处在被拖垮的风险中。 因此,为了构建稳定、可靠的分布式系统,我们的服务应当具有自我保护能力,当依赖服务不可用时,当前服务启动自我保护功能,从而避免发生雪崩效应。本文将重点介绍使用Hystrix解决同步等待的雪崩问题。
In a distributed environment, inevitably some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system’s overall resiliency. –摘自官方
译: 在分布式环境中,许多服务依赖项不可避免地会失败。Hystrix是一个库,它通过添加延迟容忍和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点、停止它们之间的级联故障以及提供后备选项来实现这一点,所有这些都可以提高系统的整体弹性。
Hystrix [hɪst’rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix是Netflix开源的一款容错框架,同样具有自我保护能力,为了实现容错和自我保护,下面我们看看Hystrix如何设计和实现的。
“熔断器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控,某个异常条件被触发,直接熔断整个服务。向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,就保证了服务调用方的线程不会被长时间占用,避免故障在分布式系统中蔓延,乃至雪崩。如果目标服务情况好转则恢复调用。服务熔断是解决服务雪崩的重要手段。
服务熔断图示:
服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此环节服务器的压力,以保证核心任务的进行。同时保证部分甚至大部分任务客户能得到正确的相应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。
通俗: 关闭系统中边缘服务 保证系统核心服务的正常运行 称之为服务降级
熔断必会触发降级,所以熔断也是降级一种,区别在于熔断是对调用链路的保护,而降级是对系统过载的一种保护处理。
<!--引入hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在启动类上加入注解
@EnableCircuitBreaker //启用断路器
注意: 这里其实也可以使用 spring cloud应用中的@SpringCloudApplication注解,因为它已经自带了这些注解,源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
加入注解
@SpringBootApplication
@EnableCircuitBreaker //用来开启断路器
public class Products9998Application {
public static void main(String[] args) {
SpringApplication.run(Products9998Application.class, args);
}
}
//服务熔断
@GetMapping("/product/break")
@HystrixCommand(fallbackMethod = "testBreakFall" )
public String testBreak(int id){
log.info("接收的商品id为: "+ id);
if(id<=0){
throw new RuntimeException("数据不合法!!!");
}
return "当前接收商品id: "+id;
}
public String testBreakFall(int id){
return "当前数据不合法: "+id;
}
从上面演示过程中会发现如果触发一定条件断路器会自动打开,过了一点时间正常之后又会关闭。那么断路器打开条件是什么呢?
A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service exceed circuitBreaker.requestVolumeThreshold (default: 20 requests) and the failure percentage is greater than circuitBreaker.errorThresholdPercentage (default: >50%) in a rolling window defined by metrics.rollingStats.timeInMilliseconds (default: 10 seconds), the circuit opens and the call is not made. In cases of error and an open circuit, a fallback can be provided by the developer.
原文翻译之后,总结打开关闭的条件:
如果为每一个服务方法开发一个降级,对于我们来说,可能会出现大量的代码的冗余,不利于维护,这个时候就需要加入默认服务降级处理方法。
package com.dp.controller; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j @DefaultProperties(defaultFallback = "testBreakFall2") //类下全局生效 public class GoodController { @GetMapping("/good/break") @HystrixCommand(fallbackMethod = "testBreakFall2") //特殊定制改接口的服务熔断方法 public String testBreak(int id) { log.info("接收的商品id为: "+ id); if(id<=0){ throw new RuntimeException("数据不合法!!!"); } return "当前接收商品id: "+id; } @GetMapping("/goodB/break") @HystrixCommand //这里不配置服务熔断方法,使用默认的处理方法 public String testBreak2(int id) { log.info("接收的商品id为: "+ id); if(id<=0){ throw new RuntimeException("数据不合法!!!"); } return "当前接收商品id: "+id; } //特殊定制的熔断处理方法 public String testBreakFall2(@RequestParam("id") int id){ return "当前数据不合法: "+id; } //默认的服务熔断全局处理方法 public String testBreakFall2(){ return "当前数据不合法; } }
测试特殊定制的服务熔断处理方法
测试默认的全局的服务熔断处理方法
可以看到一个返回了 id,一个没有返回 id。
注意:因为默认的全局服务熔断处理方法是作为一个通用方法的,不能保证参数列表中的所有参数都一样,所以必须为无参方法,否则会报错。
引入了feign之后,因为feign默认集成了Hystrix,所以无法像之前那样通过 @HystrixCommand注解绑定fallback方法一样实现服务降级。
feign提供了一种新方法:只需要为fegin客户端定义的接口编写一个实现类,那么这个实现类里每一个对应的重写方法,就是在feign远程调用该接口时,失败后,具体使用的降级方法。
feign.hystrix.enabled=true #开启openfeign支持降级
yml文件配置如下:
server:
port: 9998
spring:
application:
name: Product9998Application
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
feign:
hystrix:
enabled: true #开启openfeign支持降级
package com.dp.feignclient;
import com.dp.fallbackclient.FallBackClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "Product9998Application", fallback = FallBackClient.class)
public interface ProductFeign {
@GetMapping("/product/break")
String testBreak(@RequestParam("id") int id);
}
package com.dp.fallbackclient;
import com.dp.feignclient.ProductFeign;
import org.springframework.stereotype.Component;
@Component
public class FallBackClient implements ProductFeign {
@Override
public String testBreak(int id) {
return "我是product模块的服务降级";
}
}
注意:在服务调用的feign客户端中,通过@FeignClient注解里的fallback属性来绑定其对应降级方法的实现类。
业务层代码
package com.dp.controller; import com.dp.feignclient.ProductFeign; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j public class OrderController { @Autowired ProductFeign productFeign; @GetMapping("/get/product") public String getProduct(int id) { String result = productFeign.testBreak(id); return result; } }
结果
注意:如果服务端降级和客户端降级同时开启,要求服务端降级方法的返回值必须与客户端方法降级的返回值一致!!!
Hystrix 还提供了 准实时的调用监控(Hystrix Dashboard),Hystrix 会持续地记录所有通过 Hystrix 发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少成功,多少失败等。
Netflix 通过 hystrix-metrics-event-stream 项目实现了对以上指标的监控。同时 Spring Cloud 也提供了对 Hystrix Dashboard 的整合,将监控的内容信息转化成可视化界面。
Hystrix Dashboard的一个主要优点是它收集了关于每个HystrixCommand的一组度量。Hystrix仪表板以高效的方式显示每个断路器的运行状况。
在使用 Hystrix Dashboard 时,需要以单独工程的方式运行。以下就是 Hystrix Dashboard 的使用流程。
server:
port: 8801
spring:
application:
name: HystrixDashboard-8801
注意该依赖一般情况是配合 spring-boot-starter-acurator 同时使用,该模块主要用来完成对 服务的健康监控。在生产环境中,需要实时或定期监控服务的可用性。SpringBoot 的 actuator(健康监控)功能提供了很多监控所需的接口,可以对应用系统进行配置查看、相关功能统计等。
<!--引入hystrix dashboard 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
package com.dp; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableHystrixDashboard public class HystrixDashboard8801 { public static void main(String[] args) { SpringApplication.run(HystrixDashboard8801.class, args); } }
启动 Hystrix Dahsboard 模块项目,我们访问 http://localhost:8801/hystrix ,就能够看到 Hystrix Dashboard 豪猪哥 的监控页面了。
注意: 新版本 Hystrix 需要在主启动类中指定监控路径,如果没有此项操作,在项目启动后,Hystrix Dashboard 会报: Unable to connect to Command Metric Stream 这样一个错误。配置内容如下:
package com.dp; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableCircuitBreaker //@SpringCloudApplication public class Product9998Application { public static void main(String[] args) { SpringApplication.run(Product9998Application.class, args); } //在主启动类中指定监控路径 @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; } }
将 服务注册中心(8761端口)、服务提供方(9998端口)、Hystrix Dashboard(8801端口) 模块进行启动。然后通过 Hystrix Dashboard 8801 来监控服务端 9998。
需要在 Hystrix Dashboard 中填写 监控地址:http://localhost:9998/hystrix.stream,并完成图中四部曲:
开启监控,所以服务都启动 ok 的话,你会看到如下界面。到此处说明配置 OK。
翻译:Hystrix(版本1.5.18)足够稳定,可以满足Netflix对我们现有应用的需求。同时,我们的重点已经转移到对应用程序的实时性能作出反应的更具适应性的实现,而不是预先配置的设置(例如,通过自适应并发限制)。对于像Hystrix这样的东西有意义的情况,我们打算继续在现有的应用程序中使用Hystrix,并在新的内部项目中利用诸如resilience4j这样的开放和活跃的项目。我们开始建议其他人也这样做。
Dashboard也被废弃。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。