容错机制
- **Failover:**当出现失败时,会重试其他服务器。用户可以通过retries="2" 设置重试次数。这是Dubbo的默认容错机制,会对请求做负载均衡。通常使用在读操作或幕等的写操作上, 但在进行重试的时候,如果系统的并发比较高,那么下流的机器压力将会很大。可以通过 retries 属性来设置最大重试次数。可以设置在 dubbo: reference 标签上,也可以设置在细粒度的方法标签 dubbo:method 上。
- **Failfast :**快速失败,当请求失败后,快速返回异常结果,不做任何重试。该容错机制会对请求做负载均衡,通常使用在非幕等接口的调用上。该机制受网络抖动的影响较大。
- **Failsafe :**当出现异常时,直接忽略异常。会对请求做负载均衡。通常使用在“佛系”调用场景, 不关心调用是否成功,不抛异常影响主线程业务逻辑的执行。
- **Fallback:**请求失败,会将失败的请求记录到一个队列,然后由一个定时任务去执行重试,请求会做负载均衡。这种容错机制适合用在最终一致性的业务场景。
- **Forking :**同时调用多个相同的服务,只要其中一个返回,则立即返回结果。用户可以配置最大请求的并行数参数来确定最大并行调用的服务数量。通常使用在对接口实时性要求极高的调用上,但也会浪费更多的资源。通过 forks="最大并行数" 属性来设置最大并行数。假设设置的 forks 数为 n 可用的服务数为 v,当 n<v 时,既可用的服务数大于配置的并行数,则并行请求 n 个服务。当 n >v 时,即可用的服务数小于配置的并行数,则请求所有可用的服务 v。
- **Broadcast :**广播调用所有可用的服务,任意一个节点报错则报错。由于是广播,因此请求不需要做负载均衡。通常用于服务状态更新后的广播。
- **Mock:**提供调用失败时,返回伪造的响应结果。也可以直接强制返回伪造的结果,不会发起远程调用。这就是服务降级。
- **Available :**请求不会做负载均衡,遍历所有服务列表,找到第一个可用的节点, 直接请求并返回结果。如果没有可用的节点,则直接抛出异常。
- **Mergeable :**Mergeable可以自动把多个节点请求得到的结果进行合并。
源码阅读
需要记住的是:不论是服务降级,服务路由,还是集群容错,以及负载均衡,它们都是发生在调用提供者方法的这个地方,也就是远程调用。所以直接找InvokerInvocationHandler 类
同理,只要发起远程RPC调用,不论怎么执行,都会执行这两个中的一个。跟踪这里的 AbstractClusterInvoker 类中的 invoke 实现
Failover 策略
FailoverClusterInvoker.java
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 先复制一份Invoker
List<Invoker<T>> copyInvokers = invokers;
// 校验传入的invoke是否为空,为空则抛RPCException
checkInvokers(copyInvokers, invocation);
// 从Invoker中获取调用的方法名称
String methodName = RpcUtils.getMethodName(invocation);
// 从URL中解析出配置,重试的最大次数
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
// 如果没有配置则默认为1次
if (len <= 0) {
len = 1;
}
// 记录调用过程中出现的异常
RpcException le = null;
// 记录调用的节点,然后去做负载均衡,思路就是下次做负载的时候排除这个节点。因为这个节点已经调用过了
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
// 根据上面的次数来执行循环重试
for (int i = 0; i < len; i++) {
// 当前重试不是第一次
if (i > 0) {
// 看当前invoke是否已被销毁
checkWhetherDestroyed();
// 获取最新的invokers列表
copyInvokers = list(invocation);
// 获取完后在检查一遍 为空校验
checkInvokers(copyInvokers, invocation);
}
// 负载均衡
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try { // 远程调用
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + methodName
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyInvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
// 只要不执行到这个地方就会执行重试,当重试次数上限就抛出最下面那个RPCException异常
// 调用成功,返回结果
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
// 已执行最大重试基础,还是失败,抛出异常。
throw new RpcException(le.getCode(), "Failed to invoke the method "
+ methodName + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyInvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ le.getMessage(), le.getCause() != null ? le.getCause() : le);
}
Failfast 策略
FailfastClusterInvoker.java
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 为空校验
checkInvokers(invokers, invocation);
// 负载均衡出一个调用节点
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
try {
// 执行调用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 失败则直接抛异常
if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.
throw (RpcException) e;
}
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0,
"Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName()
+ " select from all providers " + invokers + " for service " + getInterface().getName()
+ " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost()
+ " use dubbo version " + Version.getVersion()
+ ", but no luck to perform the invocation. Last error is: " + e.getMessage(),
e.getCause() != null ? e.getCause() : e);
}
}
Failsafe 策略
FailsafeClusterInvoker.java
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 为空校验
checkInvokers(invokers, invocation);
// 负载均衡出调用的节点
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
// 进行调用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 吃掉异常。返回一个AsyncRpcResult结果集
logger.error("Failsafe ignore exception: " + e.getMessage(), e);
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
Fallback 策略
FailbackClusterInvoker.java
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
Invoker<T> invoker = null;
try {
// 为空校验
checkInvokers(invokers, invocation);
// 负载均衡出一个调用节点
invoker = select(loadbalance, invocation, invokers, null);
// 发起调用
return invoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
+ e.getMessage() + ", ", e);
// 发生异常是,定时任务执行重试
addFailed(loadbalance, invocation, invokers, invoker);
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {
// 是否有任务,如果没有则创建。有 则复用之前创建好的任务
if (failTimer == null) {
synchronized (this) {
if (failTimer == null) {
failTimer = new HashedWheelTimer(
new NamedThreadFactory("failback-cluster-timer", true),
1,
TimeUnit.SECONDS, 32, failbackTasks);
}
}
}
// 初始化任务线程
RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD);
try {
// 创建定时任务并添加到队列
failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
} catch (Throwable e) {
logger.error("Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage());
}
}
private class RetryTimerTask implements TimerTask {
private final Invocation invocation;
private final LoadBalance loadbalance;
private final List<Invoker<T>> invokers;
private final int retries;
private final long tick;
private Invoker<T> lastInvoker;
private int retryTimes = 0;
RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker, int retries, long tick) {
this.loadbalance = loadbalance; // 负载均衡算法
this.invocation = invocation; // 远程RPC调用的信息
this.invokers = invokers; // 集群节点服务列表
this.retries = retries; // 设置的最大重试次数
this.tick = tick; // 重试间隔时间 秒
this.lastInvoker=lastInvoker; // 最后一次调用的时候使用的服务节点
}
@Override
public void run(Timeout timeout) {
try {
// 轮询出调用的节点
Invoker<T> retryInvoker = select(loadbalance, invocation, invokers, Collections.singletonList(lastInvoker));
// 将本地选出的服务节点记录成最后一次调用的服务节点
lastInvoker = retryInvoker;
retryInvoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
if ((++retryTimes) >= retries) {
// 重试次数已经上限
logger.error("Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation);
} else {
// 再添加一次任务
rePut(timeout);
}
}
}
private void rePut(Timeout timeout) {
if (timeout == null) {
return;
}
Timer timer = timeout.timer();
if (timer.isStop() || timeout.isCancelled()) {
return;
}
timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS);
}
}
Available 策略
AvailableClusterInvoker.java
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 遍历所有的服务节点列表
for (Invoker<T> invoker : invokers) {
// 只要是可用的服务就进行调用。不会进行负载均衡
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
Broadcast 策略
BroadcastClusterInvoker.java
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 为空校验
checkInvokers(invokers, invocation);
// 记录RPC的上下文消息
RpcContext.getContext().setInvokers((List) invokers);
RpcException exception = null;
Result result = null;
// 遍历整个服务节点列表
for (Invoker<T> invoker : invokers) {
try {
// 进行调用
result = invoker.invoke(invocation);
//如果在调用的过程中出现异常则直接抛出
} catch (RpcException e) {
exception = e;
logger.warn(e.getMessage(), e);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e);
logger.warn(e.getMessage(), e);
}
}
if (exception != null) {
throw exception;
}
return result;
}
Forking 策略
ForkingClusterInvoker.java
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 为空校验
checkInvokers(invokers, invocation);
final List<Invoker<T>> selected;
// 从URL中解析配置,如果URL中没有配置则返回默认值。 (最大并行数,超时时间)
final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS);
final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
// 假设用户设置最大的并行数为: N 服务节点为: V
// 则: N <=0 或者 N >= V。则最终调用的Invoker只能是N个
if (forks <= 0 || forks >= invokers.size()) {
selected = invokers;
} else {
// N <= V
// 初始化一个列表, 经过负载均衡选出invoker并加入到列表中
selected = new ArrayList<>();
for (int i = 0; i < forks; i++) {
Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
// 监测选出来的invoker是否已经存在于列表中
if (!selected.contains(invoker)) {
selected.add(invoker);
}
}
}
RpcContext.getContext().setInvokers((List) selected);
// 初始化一个异常计数器
final AtomicInteger count = new AtomicInteger();
// 初始化一个阻塞队列,用于记录并行调用的结果。
final BlockingQueue<Object> ref = new LinkedBlockingQueue<>();
// 执行调用
for (final Invoker<T> invoker : selected) {
executor.execute(new Runnable() {
@Override
public void run() {
try {
Result result = invoker.invoke(invocation);
// 成功将调用结果添加到队列
ref.offer(result);
} catch (Throwable e) {
// 如果失败次数 >= V。 (说明是最后一个服务节点了,才会将异常记录到队列中)
int value = count.incrementAndGet();
if (value >= selected.size()) {
// 失败将异常记录到队列
ref.offer(e);
}
}
}
});
}
try {
// 在指定时间(秒)后弹出队列的首个元素
Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
// 如果是异常则抛出。RPCException会产生降级
if (ret instanceof Throwable) {
Throwable e = (Throwable) ret;
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
}
// 否则则返回调用结果
return (Result) ret;
} catch (InterruptedException e) {
throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e);
}
} finally {
// clear attachments which is binding to current thread.
RpcContext.getContext().clearAttachments();
}
}