雪崩效应

在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。

如果下图所示:A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者。A不可用引起了B的不可用,并将不可用像滚雪球一样放大到C和D时,雪崩效应就形成了。

如何容错

要想防止雪崩效应,必须有一个强大的容错机制。该机制需实现以下两点:

  • 为网络请求设置超时:必须为网络请求设置超时。正常情况下,一个远程调用一般在几十毫秒内就能得到响应。如果依赖的服务不可用或者网络有问题,那么响应时间就会变得很长(几十秒)。通常情况下,一次远程调用对应着一个线程或进程。如果响应太慢,这个线程或进程就得不到释放。而线程或进程又对应着系统资源,如果得不到释放的线程或进程越积越多,资源就会逐渐被耗尽,最终导致服务的不可用。因此,必须为每个网络请求设置超时,让资源尽快释放。
  • 使用断路模式:试想一下,如果家里没有断路器,当电流过载时(例如功率过大、断路等),电路不断开,电路就会升温,甚至可能烧断电路、引发火灾。使用断路器,电路一旦过载就会跳闸,从而可以保护电路的安全。在电路超载的问题被解决后,只需关闭断路器,电路就可以恢复正常。

同理,如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有意义了,只会无畏的消耗资源。例如,设置了超时时间为1s,如果短时间内有大量的请求无法在1s内得到响应,就没有必要再去请求依赖的服务了。

断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。

断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无需再浪费CPU时间去等待长时间的超时。

断路器也可自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复”——当依赖的服务不正常时,打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。

断路器状态转换的逻辑图:

简单说一下:

正常情况下,断路器关闭,可以正常请求依赖的服务。

当一段时间内,请求的失败率达到一定阈值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。

断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。

综上,我们可通过以上两点机制保护应用,从而防止雪崩效应并提升应用的可用性。

Hystrix简介

Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。
  • 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往改以来的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时和被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换,见上图。

代码编写

1.我们复制microservice-consumer-movie-ribbon,将ArtifactId修改为microservice-consumer-movie-ribbon-hystrix。

2.为项目添加依赖。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix- hystrix</artifactId>
</dependency>

3.在启动类上添加注解@EnableCircuitBreaker或@EnableHystrix,从而为项目开启断路器支持。

4.修改MovieController,让其中的findById方法具备容错能力。

@RestController
public class MovieController { private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
@Autowired
private RestTemplate restTemplate; @Autowired
private LoadBalancerClient loadBalancerClient; @HystrixCommand(fallbackMethod = "findByIdFallback")
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return restTemplate.getForObject("http://microservice-provider-user/" + id, User.class);
} public User findByIdFallback(Long id) {
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
} @GetMapping("log-user-instance")
public void logUserInstance() {
ServiceInstance serviceInstance = loadBalancerClient.choose("microservice-provider-user");
LOGGER.info("{}:{}:{}", serviceInstance.getServiceId(), serviceInstance.getHost(), serviceInstance.getPort()); } }

由代码可知,为findById方法编写了一个回退方法findByIdFallback,该方法与findById方法具有相同的参数与返回类型,该方法返回了一个默认的User。

在findById方法上,使用注解@HystrixCommand的fallbackMethod属性,指定回退方法是findByIdFallback。

测试

启动项目microservice-discovery-eureka。

启动项目microservice-provider-user。

启动项目,microservice-consumer-movie-ribbon-hystrix。

访问http://localhost:8082/user/1,结果:

{"id":1,"username":"account1","name":"张三","age":20,"balance":98.23}

断开microservice-provider-user,再次访问 http://localhost:8082/user/1,结果:

{"id":-1,"username":null,"name":"默认用户","age":null,"balance":null}

说明当前用户微服务不可用,进入了回退方法。


异常展示

很多时候,我们需要获得造成回退的原因,只需再fallback方法上添加一个Throwable参数即可:

public User findByIdFallback(Long id ,Throwable throwable) {
LOGGER.error("进入回退方法,异常:", throwable);
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}

断开microservice-provider-user,再次访问 http://localhost:8082/user/1,看电影微服务的控制台:

2019-04-23 19:34:05.539 ERROR 10392 --- [ieController-10] c.i.cloud.controller.MovieController     : 进入回退方法,异常:

java.lang.IllegalStateException: No instances available for microservice-provider-user
at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:75) ~[spring-cloud-netflix-core-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor.intercept(LoadBalancerInterceptor.java:55) ~[spring-cloud-commons-1.3.0.RELEASE.jar:1.3.0.RELEASE]
at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:86) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.cloud.netflix.metrics.MetricsClientHttpRequestInterceptor.intercept(MetricsClientHttpRequestInterceptor.java:64) ~[spring-cloud-netflix-core-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:86) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:70) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:659) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:620) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:294) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.web.client.RestTemplate$$FastClassBySpringCGLIB$$aa4e9ed0.invoke(<generated>) ~[spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:738) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:85) ~[spring-aop-4.3.13.RELEASE.jar:4.3.13.RELEASE]
at org.springframework.cloud.netflix.metrics.RestTemplateUrlTemplateCapturingAspect.captureUrlTemplate(RestTemplateUrlTemplateCapturingAspect.java:33) ~[spring-cloud-netflix-core-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at sun.reflect.GeneratedMethodAccessor94.invoke(Unknown Source) ~[na:na]

还有一种情况,当发生业务异常时,我们不想触发fallback。此时要怎么办呢?Hystrix有个HystrixBanRequestException类,这是一个特殊的异常类,当该异常发生时,不会触发回退。因此,可将自定义的业务异常继承该类,从而达到业务异常不回退的效果。

另外,@HystrixCommand为我们提供了ignoreExceptions属性,也可借助该属性来配置不想执行回退的异常类。例如:

@HystrixCommand(fallbackMethod = "findByIdFallback",ignoreExceptions= {IllegalArgumentException.class})
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return restTemplate.getForObject("http://microservice-provider-user/" + id, User.class);
}

多个异常类用逗号隔开。

这样,即使在findById中发生IllegalArgumentException,也不会执行findByIdFallback方法。


代码下载地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/microservice-consumer-movie-ribbon-hystrix.zip

最新文章

  1. PHP设计模式(四)单例模式(Singleton For PHP)
  2. kibana 使用
  3. leetcode 172
  4. IO调度器
  5. poj 3321 Apple Tree dfs序+线段树
  6. Vectoroid
  7. HDU 4679 Terrorist’s destroy
  8. SLAM reference
  9. HOG参数简介及Hog特征维数的计算(转)
  10. JavaScript对象的创建之外部属性定义方式(基于已有对象扩充其属性和方法)
  11. Java基础知识强化104:Serializable接口 与 Parcelable接口
  12. [译]36 Days of Web Testing(五)
  13. Coding Dojo
  14. 笑谈ArcToolbox (3) ArcToolbox的一亩三分地
  15. Struts中的找不到StringUtils异常
  16. Spark学习之在集群上运行Spark
  17. ubuntu 完全卸载mysql
  18. commons-lang3工具类学习(二)
  19. Jenkins中使用Azure Powershell连接Service Fabric报错not recognized的原因与解决办法
  20. 每日质量NPM包模态框_react-modal

热门文章

  1. vue项目 菜单侧边栏随着右侧内容盒子的高度实时变化
  2. [转]【流媒體】H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流
  3. FTP服务器的搭建与安全配置
  4. 解决部分版本kali升级后w3af无法运行的问题
  5. java中创建对象的方法
  6. threadpoolExecutor----自动执行任务
  7. Cockroachdb 二、手动部署
  8. [Erlang25]Erlang in anger 翻译
  9. linux的定制和发布(一)
  10. Linux中连接mysql执行sql文件