文章目录
- 前言
- 内置容错策略
- Failover(失败自动切换)
- Failsafe(失败安全)
- Failfast(快速失败)
- Failback(失败自动恢复)
- Forking(并行调用)
- Broadcast(广播调用)
- 集成断路器 Hystrix
- 服务降级与服务熔断的区别
前言
在分布式系统中,集群中的一些节点出现问题并不是什么稀奇的事情,所以我们在设计分布式 RPC 框架的时候,应该重点考虑失败问题。在调用失败之后,应该如何选择对失败的处理策略,这是一个问题。Dubbo 为我们提供了多种策略,每一种策略对应一种场景,以供我们选择。
Dubbo 会为我们提供一组可调用的服务提供者,在经过路由规则过滤,负载均衡选址之后,选中一个具体地址进行调用,若调用失败,则会按照集群配置的容错策略进行容错处理。
内置容错策略
Dubbo默认内置了一些容错策略,如果还不能满足用户需求,我们可以自定义容错策略进行配置。Dubbo 内置了以下几种容错策略:
- Failover(失败自动切换)
- Failsafe(失败安全)
- Failfast(快速失败)
- Failback(失败自动恢复)
- Forking(并行调用)
- Broadcast(广播调用)
下面我们分别对其进行介绍。
Failover(失败自动切换)
Failover 是 Dubbo 默认的容错策略。
其实,Failover 是高可用的一个常用概念,服务器通常拥有主备两套机器配置,当主服务器出现故障时,会自动切换到备服务器中,从而保证了整体的高可用性。
当调用失败时,会根据配置的重试次数,自动从其他可用地址中重新选择一个可用的地址进行调用,直到调用成功,或者是达到重试的上限位置。
Failover 会自动对失败进行重试,但它也带来了一些副作用。首先,重试会增加开销,再者,重试会增加调用的响应时间,最后,在某些情况下,重试会造成资源的浪费。
Failsafe(失败安全)
Failsafe 在调用失败时,会忽略此错误,并记录一条日志,同时返回一个空结果,在上游看来调用是成功的。
Failsafe 即使失败了也不会影响整个调用流程,它的失败不影响核心业务的正确性,通常用于旁路系统或流程中,一般用于写入审计日志等操作。
Failfast(快速失败)
有一些业务场景中,其操作是非幂等的,不能重复调用。这种情况下,重试并不是一个好办法,需要用到 Failfast,调用失败立即报错,让调用方来决定下一步的操作并保证业务的幂等性。
Failback(失败自动恢复)
在 Failback 中,如果调用失败,则此次失败相当于 Failsafe,将返回一个空结果,但与 Failsafe 不同的是,Failback 策略会将这次调用加入内存中的失败列表中,对于这个列表中的失败调用,会在另一个线程中进行异步重试,重试如果再发生失败,则会忽略,即使重试调用成功,原来的调用方也感知不到了。因此它通常适合于对于实时性要求不高,且不需要返回值的一些异步操作。
Forking(并行调用)
Forking 在第一次调用就同时发起多个调用,只要其中一个调用成功,就认为成功。在资源充足,且对于失败的容忍度较低的场景下,可以采用此策略。
Broadcast(广播调用)
在某些场景下,我们可能需要对所有服务提供者进行操作,我们可以采用广播调用策略,会逐个调用所有提供者,只要任意有一个提供者出错,则认为此次调用出错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集成断路器 Hystrix
在开发中,我们一般会在 Dubbo 中集成断路器 Hystrix 做集群容错。
在服务提供者与服务消费者中导入 Hystrix 的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
然后在服务提供者的启动类添加 @EnableHystrix 注解
package edu.szu.producer;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableDubbo
@EnableHystrix
public class ProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ProducerApplication.class, args);
}
}
在可能出现异常的方法添加 @HystrixCommand 注解,表示该方法会经过 Hystrix 代理。
package edu.szu.producer.serviceImpl;
import com.alibaba.dubbo.config.annotation.Service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import edu.szu.api.service.NameService;
import org.springframework.stereotype.Component;
@Component
@Service
public class NameServiceImpl implements NameService {
@Override
@HystrixCommand
public String updateName(String name) {
return "远程调用的值:" + name;
}
}
在服务消费者的启动类添加 @EnableHystrix 注解
package edu.szu.consumer;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
@SpringBootApplication
@EnableDubbo
@EnableHystrix
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
然后在调用方法上增加 @HystrixCommand 注解,并指定 fallbackMethod ,该属性指定出错之后调用的方法。
package edu.szu.consumer.serviceImpl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import edu.szu.api.service.NameService;
import edu.szu.consumer.service.ChangeService;
import org.springframework.stereotype.Component;
@Component
public class ChangeServiceImpl implements ChangeService {
@Reference
NameService nameService;
@Override
@HystrixCommand(fallbackMethod = "use")
public String change(String name) {
return nameService.updateName(name);
}
public String use(String name) {
return "服务熔断:" + name;
}
}
这时我们在服务提供者中显式抛出一个异常。
package edu.szu.producer.serviceImpl;
import com.alibaba.dubbo.config.annotation.Service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import edu.szu.api.service.NameService;
import org.springframework.stereotype.Component;
@Component
@Service
public class NameServiceImpl implements NameService {
@Override
@HystrixCommand
public String updateName(String name) {
if(Math.random() > 0.5) {
throw new RuntimeException();
}
return "远程调用的值:" + name;
}
}
远程调用十次,其返回值分别如下:
远程调用的值:HelloDubbo
远程调用的值:HelloDubbo
远程调用的值:HelloDubbo
远程调用的值:HelloDubbo
服务熔断:HelloDubbo
服务熔断:HelloDubbo
服务熔断:HelloDubbo
服务熔断:HelloDubbo
服务熔断:HelloDubbo
远程调用的值:HelloDubbo
可见 Dubbo 与断路器 Hystrix 的集成成功。
服务降级与服务熔断的区别
在我们的上一篇博客提到过 Dubbo 如何进行服务降级,在本博客中我们又提到了 Dubbo 如何集成 Hystrix 做服务熔断的功能,那么,这两者有什么区别呢?
我们可以将服务熔断抽象为保险丝,如果服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。而服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些服务出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而会直接返回一个提前准备好的错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
总结一下,熔断与降级都是从可用性和可靠性出发,为了防止系统崩溃,最终都让用户体验到的是某些功能暂时不可用,它们的区别在于服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑。