赞
踩
服务降级指的是当服务器压力剧增的情况下,为了保证核心功能的可用性 ,而选择性的降低一些功能的可用性,或者直接关闭该功能。
服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的 fallback 兜底处理。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断和降级是两个比较容易混淆的概念,两者的含义并不相同。降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。
限流是从用户访问压力的角度来考虑如何应对系统故障。
限流为了对服务端的接口接受请求的频率进行限制,防止服务挂掉。比如某一接口的请求限制为 100 个每秒, 对超过限制的请求放弃处理或者放到队列中等待处理。限流可以有效应对突发请求过多。
分布式系统面临的问题:微服务架构中,一个应用会根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),这些服务之间相互依赖,依赖关系错综复杂。当微服务系统的一个服务出现故障时,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。由于服务与服务之间的依赖性,故障会沿着服务的调用链路在系统中蔓延,最终导致整个微服务系统的瘫痪,这就是“雪崩效应”。为了防止此类事件的发生,微服务架构引入了“熔断器”的一系列服务容错和保护机制。
熔断器本身是一种开关装置,当某个服务发生故障后,通过断路器的故障监控,向服务调用方返回一个符合预期的、可处理的降级响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常。这样就保证了服务调用方的线程不会被长时间、不必要地占用,避免故障在微服务系统中的蔓延,防止系统雪崩效应的发生。
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,提供了熔断器功能。在分布式系统里,许多依赖服务不可避免的会调用失败,比如超时、异常等。Hystrix 能够保证在一个依赖服务出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
Hystrix 提供了服务降级功能,能够保证当前服务不受其他服务故障的影响,提高服务的健壮性。
服务降级的使用场景有以下 2 种:
Hystrix 服务降级 FallBack 既可以放在服务端进行,也可以放在客户端进行。
Hystrix 会在以下场景下进行服务降级处理:程序运行异常、服务超时、服务宕机。
熔断机制是为了应对雪崩效应而出现的一种微服务链路保护机制。
当微服务系统中的某个微服务不可用或响应时间太长时,为了保护系统的整体可用性,熔断器会暂时切断请求对该服务的调用,并快速返回一个友好的错误响应。这种熔断状态不是永久的,在经历了一定的时间后,熔断器会再次检测该微服务是否恢复正常,若服务恢复正常则恢复其调用链路。
在熔断机制中涉及了三种熔断状态:
业务类测试
@Service
public class PaymentHystrixService {
//正常访问
public String paymentInfo_OK(Integer id) {
return "线程池: " + Thread.currentThread().getName() +
", paymentInfo_OK, id = " + id;
}
//超时响应
public String paymentInfo_TimeOut(Integer id) {
int timeout = 3;
TimeUnit.SECONDS.sleep(timeout);
return "线程池: " + Thread.currentThread().getName() +
", paymentInfo_TimeOut, id = " + id + ", 耗时" + timeout + "秒";
}
}
Controller 测试
@Slf4j @RestController @RequestMapping("/payment") public class PaymentHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @Value("${server.port}") private String serverPort; @GetMapping("/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_OK(id); log.info("result: " + result); return result; } @GetMapping("/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentHystrixService.paymentInfo_TimeOut(id); log.info("result: " + result); return result; } }
引入依赖,yml 与主启动类不变。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
OpenFeign 接口
@Component
@FeignClient(value = "PROVIDER-PAYMENT")
public interface PaymentFeignService {
//整合hystrix熔断器
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
Controller 接口
@Slf4j @RestController @RequestMapping("/order") public class OrderFeignHystrixController { @Resource private PaymentFeignService paymentFeignService; @GetMapping("/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { String result = paymentFeignService.paymentInfo_OK(id); log.info("result: " + result); return result; } @GetMapping("/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { String result = paymentFeignService.paymentInfo_TimeOut(id); log.info("result: " + result); return result; } }
解决:
服务提供方先从自身找问题,设置自身调用超时时间的上限,上限内可以正常运行,超过了需要有兜底的方法处理,作服务降级 fallback。
修改业务类,使用 @HystrixCommand
与 @HystrixProperty
注解。
@Service public class PaymentHystrixService { //一旦该方法失败并抛出了异常信息后,会自动调用@HystrixCommand注解标注的fallbackMethod指定的方法 @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = { //规定3秒钟以内就不报错,正常运行,超过3秒就报错 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_TimeOut(Integer id) { int timeout = 5; //int i = 1 / 0; //直接报错也可触发降级 TimeUnit.SECONDS.sleep(timeout); return "线程池: " + Thread.currentThread().getName() + ", paymentInfo_TimeOut, id = " + id + ", 耗时" + timeout + "秒"; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池: " + Thread.currentThread().getName() + ", paymentInfo_TimeOutHandler, id = " + id + "系统繁忙或运行报错,请稍后重试"; } }
主启动类激活熔断器
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableCircuitBreaker //激活熔断器功能
public class PaymentApplication {
80 订单微服务,也可以用降级保护,更好的保护自己。实际业务一般服务降级都放在这种消费者端/客户端。
application.yml 中添加以下配置,开启客户端的 Hystrix 功能。
#开启Hystrix熔断器功能
feign:
hystrix:
enabled: true
主启动类启用 Hystrix。
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //开启 OpenFeign 功能
@EnableHystrix //启用 Hystrix
public class OrderFeignApplication {
Feign 调用降级。
@RestController @RequestMapping("/order") public class OrderFeignHystrixController { @Resource private PaymentFeignService paymentFeignService; @GetMapping("/hystrix/timeout/{id}") //一旦该方法失败并抛出了异常信息后,会自动调用@HystrixCommand注解标注的fallbackMethod指定的方法 @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = { //规定使用feign调用服务,1.5秒以内返回就不报错,正常运行;超过1.5秒就报错,调用兜底降级方法 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //支付服务处理业务需要3秒,虽然支付服务规定在5秒内正常,但客户端规定1.5秒内返回,故会报错 return paymentFeignService.paymentFeignTimeout(); } public String paymentTimeOutFallbackMethod(Integer id) { return "我是订单消费者80,对方支付系统繁忙,请稍后重试"; } }
目前问题 1:每个业务方法对应一个兜底的方法,代码膨胀。
除了个别重要核心业务有专属降级方法,其它普通的可以通过 @DefaultProperties(defaultFallback = “”)
统一跳转到统一处理结果页面。通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量。
在类名上标注 @DefaultProperties
注解,并通过其 defaultFallback
属性指定一个全局的降级方法。
@RestController @RequestMapping("/order") @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //指定全局降级方法 public class OrderFeignHystrixController { @Resource private PaymentFeignService paymentFeignService; @GetMapping("/hystrix/timeout/{id}") @HystrixCommand //使用全局降级方法,不加该注解则不使用降级方法 public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { int i = 1 / 0; return paymentFeignService.paymentFeignTimeout(); } //全局降级方法 public String payment_Global_FallbackMethod() { return "全局异常处理信息,请稍后再试~"; } }
注意:全局降级方法的优先级较低,只有业务方法没有指定其降级方法,并且加上
@HystrixCommand
注解时,服务降级时才会触发全局降级方法。若业务方法指定它自己的降级方法,那么在服务降级时,就只会直接触发它自己的降级方法,而非全局降级方法。注意:降级(FallBack)方法必须与其对应的业务方法在同一个类中,否则无法生效。
全局降级方法默认超时时间为 1 秒。可以直接指定 commandProperties。
@GetMapping("/hystrix/timeout/{id}")
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
value = "5000")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
return paymentFeignService.paymentInfo_TimeOut(id);
}
目前问题 2:不管是业务方法指定的降级方法还是全局降级方法,它们都必须和业务方法在同一个类中才能生效,业务逻辑与降级逻辑耦合度极高。
只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可实现对业务逻辑与降级逻辑的解耦。
@Component
public class PaymentFallbackService implements PaymentFeignService{
@Override
public String paymentInfo_OK(Integer id) {
return "paymentInfo_OK PaymentFallbackService fallback";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "paymentInfo_TimeOut PaymentFallbackService fallback";
}
}
@FeignClient 指定 fallback 类。
@Component
@FeignClient(value = "PROVIDER-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentFeignService {
//整合hystrix熔断器
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
业务类配置服务熔断。
@Service public class PaymentHystrixService { @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = { //以下参数在 HystrixCommandProperties 类中有默认配置 //是否开启熔断器 @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //统计时间窗(默认10秒) @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1000"), //统计时间窗内请求次数 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), //休眠时间窗口期 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), //在统计时间窗口期以内,请求失败率达到60%时进入熔断状态 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), }) public String paymentCircuitBreaker(Integer id) { if (id < 0) { throw new RuntimeException("id不能为负数"); } return Thread.currentThread().getName() + "调用成功,流水号:" + UUID.randomUUID(); } public String paymentCircuitBreaker_fallback(Integer id) { return "请稍后再试, id = " + id; } }
在以上代码中,共涉及到了 4 个与 Hystrix 熔断机制相关的重要参数,这 4 个参数的含义如下表。
参数 | 描述 |
---|---|
timeInMilliseconds | 统计时间窗(单位毫秒),默认10秒。 |
sleepWindowInMilliseconds | 休眠时间窗,熔断开启状态持续一段时间后,熔断器会自动进入半熔断状态,这段时间就被称为休眠窗口期。 |
requestVolumeThreshold | 请求总数阀值。 在统计时间窗内,请求总数必须到达一定的数量级,Hystrix 才可能会将熔断器打开进入熔断开启转态,而这个请求数量级就是 请求总数阀值。Hystrix 请求总数阈值默认为 20,这就意味着在统计时间窗内,如果服务调用次数不足 20 次,即使所有的请求都调用出错,熔断器也不会打开。 |
errorThresholdPercentage | 错误百分比阈值。 当请求总数在统计时间窗内超过了请求总数阀值,且请求调用出错率超过一定的比例,熔断器才会打开进入熔断开启转态,而这个比例就是错误百分比阈值。错误百分比阈值设置为 50,就表示错误百分比为 50%,如果服务发生了 30 次调用,其中有 15 次发生了错误,即超过了 50% 的错误百分比,这时候将熔断器就会打开。 |
@RestController
@RequestMapping("/payment")
public class PaymentHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
//==========服务熔断==========
@GetMapping("/hystrix/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
String result = paymentHystrixService.paymentCircuitBreaker(id);
log.info("result: " + result);
return result;
}
}
断路器开启或者关闭的条件:
Hystrix 还提供了准实时的调用监控(Hystrix Dashboard)功能,Hystrix 会持续地记录所有通过 Hystrix 发起的请求的执行信息,并以统计报表的形式展示给用户,包括每秒执行请求的数量、成功请求的数量和失败请求的数量等。
新开微服务 9001 端口。
<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>
@SpringBootApplication
@EnableHystrixDashboard //开启 Hystrix 监控功能
public class HystrixDashboardApplication {
9001 监控 8001:http://localhost:8001/hystrix.stream
需要在被监控的 8001 端口的服务增加如下配置类
@Configuration public class HystrixDashboardConfig { /** *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑 *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream", *只要在自己的项目里配置上下面的servlet就可以了 *否则,Unable to connect to Command Metric Stream 404 */ @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; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。