文章目录

  • 前言
  • 内置容错策略
  • Failover(失败自动切换)
  • Failsafe(失败安全)
  • Failfast(快速失败)
  • Failback(失败自动恢复)
  • Forking(并行调用)
  • Broadcast(广播调用)
  • 集成断路器 Hystrix
  • 服务降级与服务熔断的区别


前言

在分布式系统中,集群中的一些节点出现问题并不是什么稀奇的事情,所以我们在设计分布式 RPC 框架的时候,应该重点考虑失败问题。在调用失败之后,应该如何选择对失败的处理策略,这是一个问题。Dubbo 为我们提供了多种策略,每一种策略对应一种场景,以供我们选择。

Dubbo 会为我们提供一组可调用的服务提供者,在经过路由规则过滤,负载均衡选址之后,选中一个具体地址进行调用,若调用失败,则会按照集群配置的容错策略进行容错处理。

内置容错策略

Dubbo默认内置了一些容错策略,如果还不能满足用户需求,我们可以自定义容错策略进行配置。Dubbo 内置了以下几种容错策略:

  1. Failover(失败自动切换)
  2. Failsafe(失败安全)
  3. Failfast(快速失败)
  4. Failback(失败自动恢复)
  5. Forking(并行调用)
  6. 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 做服务熔断的功能,那么,这两者有什么区别呢?

我们可以将服务熔断抽象为保险丝,如果服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。而服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些服务出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而会直接返回一个提前准备好的错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。

总结一下,熔断与降级都是从可用性和可靠性出发,为了防止系统崩溃,最终都让用户体验到的是某些功能暂时不可用,它们的区别在于服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑。