实现容错的手段
如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不作任何处理,此类问题可能会导致服务消费者的资源耗竭甚至整个系统的崩溃。
雪崩效应
微服务架构的应用系统通常包含多个服务层。微服务之间通过网络进行通信,从而支撑起整个应用系统,因此,微服务之间难免存在依赖关系。我们知道,任何微服务都并非100%可用,网络往往也很脆弱,因此难免有些请求会失败。
我们常把“基础服务故障”导致“级联故障”的现象称为雪崩效应。雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。
如何容错
要想防止雪崩效应,必须有一个强大的容错机制。该容错机制需实现以下两点。
- 为网络请求设置超时
必须为网络请求设置超时。正常情况下,一个远程调用一般在几十毫秒内就能得到响应了。如果依赖的服务不可用或者网络有问题,那么响应时间就会变得很长(几十秒)。
通常情况下,一次远程调用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,如果得不到释放的线程/进程越积越多,资源就会逐渐被耗尽,最终导致服务的不可用。
因此,必须为每个网络请求设置超时,让资源尽快释放。 - 使用断路器模式
试想一下,如果家里没有断路器,当电流过载时(例如功率过大、短路等),电路不断开,电路就会升温,甚至可能烧断电路、引发火灾。使用断路器,电路一旦过载就会跳闸,从而可以保护电路的安全。在电路超载的问题被解决后,只须关闭断路器,电路就可以恢复正常。
同理,如果对某个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该服务已经没有任何意义,只会无谓消耗资源。例如,设置了超时时间为1秒,如果短时间内有大量的请求无法在1秒内得到响应,就没有必要再去请求依赖的服务了。
断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
断路器可以实现快速失败,如果它在一段时间内检测到许多类似的错误(例如超时),就会在之后的一段时间内,强迫对该服务的调用快速失败,即不再请求所依赖的服务。这样,应用程序就无须再浪费CPU时间去等待长时间的超时。
断路器也可自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复”——当依赖的服务不正常时打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。
- 正常情况下,断路器关闭,可正常请求依赖的服务。
- 当一段时间内,请求失败率达到一定阈值(例如错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
- 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。
综上,我们可通过以上两点机制保护应用,从而防止雪崩效应并提升应用的可用性。
使用Hystrix实现容错
Hystrix是一个实现了超时机制和断路器模式的工具类库。
Hystrix简介
Hystrix是由Netflix 开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
- 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用到了设计模式中的“命令模式”。
- 跳闸机制:当某服务的错误率超过一定阀值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
- 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
- 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
- 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑可由开发人员自行提供,例如返回一个缺省值。
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态。断路器打开、关闭、半开的逻辑转换。
整合Hystrix
由于Springboot2.1.1 Feign 已经集成了hystrix服务,所以我们这里就不需要添加新的依赖。但是如果你项目中使用的不是Feign而是使用Ribbon+RestTemplate,那么就需要在pom文件中新加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<artifactId>spring-cloud-starter-hystrix</artifactId>
已经过期,推荐使用<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
@FeignClient注解中增加fallback
- 改造cloud-service-order
package com.cc.cloud.order.feign;
import com.cc.cloud.order.feign.fallback.MemberFeignFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient(value = "cloud-service-member", fallback = MemberFeignFallback.class)
public interface MemberFeign {
@RequestMapping("/member/members")
List<String> getAllMemberList();
}
package com.cc.cloud.order.feign.fallback;
import com.cc.cloud.order.feign.MemberFeign;
import com.google.common.collect.Lists;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class MemberFeignFallback implements MemberFeign {
//服务降级处理
@Override
public List<String> getAllMemberList() {
List<String> memberList = Lists.newArrayList();
memberList.add("not member list");
return memberList;
}
}
- 改造cloud-service-member
package com.cc.cloud.member.feign;
import com.cc.cloud.member.feign.fallback.OrderFeignFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient(value = "cloud-service-order",fallback = OrderFeignFallback.class)
public interface OrderFeign {
@RequestMapping("/order/orders")
List<String> getAllOrderList();
}
package com.cc.cloud.member.feign.fallback;
import com.cc.cloud.member.feign.OrderFeign;
import com.google.common.collect.Lists;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class OrderFeignFallback implements OrderFeign {
//服务降级处理
@Override
public List<String> getAllOrderList() {
List<String> listUser = Lists.newArrayList();
listUser.add("not order list");
return listUser;
}
}
Feign默认为开启hystrix服务,各个不同版本可能不一致,所以为了保险起见,在配置文件中开启hystrix服务,其中feign.hystrix.enabled=true
代表hystrix服务开启,false关闭hystrix服务。
所以我们给cloud-service-member和cloud-service-order加上配置
eureka:
client:
service-url:
defaultZone: http://localhost:8888/eureka/
server:
port: 8762
spring:
application:
name: cloud-service-member
feign:
hystrix:
enabled: true
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
server:
port: 8765
spring:
application:
name: cloud-service-order
feign:
hystrix:
enabled: true
现在我们可以启动我们的服务。
我们现在是可以正常访问两个服务。
然后我们关掉order服务之后访问member服务。
同理,我们关掉member服务,访问order服务。
至此断路器已经启作用了
参考
SpringCloud与Docker微服务架构实战-完整版.pdf
【SpringCloud Greenwich版本】第五章:断路器(hystrix)