Dubbo 2.7 集群容错策略源码解析
学习目标
完成本章后,你可以:
- 阐述6种集群容错策略的核心差异与适用场景
- 理解FailoverClusterInvoker的重试计数与Provider切换逻辑
- 说明Failback的异步定时重试实现机制
- 对比Forking并行调用与Broadcast逐一遍历的资源消耗
1. Cluster扩展体系
java
/**
* Cluster ------ 集群容错策略的SPI扩展点
*
* @SPI(FailoverCluster.NAME)
* public interface Cluster {
* <T> Invoker<T> join(Directory<T> directory) throws RpcException;
* }
*
* 每个Cluster实现负责将Directory转换为一个特定容错的Invoker包装
*
* 实现关系:
* Cluster.join(directory)
* → FailoverCluster(默认) → FailoverClusterInvoker
* → FailfastCluster → FailfastClusterInvoker
* → FailsafeCluster → FailsafeClusterInvoker
* → FailbackCluster → FailbackClusterInvoker
* → ForkingCluster → ForkingClusterInvoker
* → BroadcastCluster → BroadcastClusterInvoker
* → AvailableCluster → AvailableClusterInvoker
* → MergeableCluster → MergeableClusterInvoker
*/
2. FailoverClusterInvoker------默认策略
java
/**
* FailoverClusterInvoker ------ 失败自动切换(默认策略)
*
* 算法:
* ① 通过Directory获取可用Provider列表
* ② 通过LoadBalance选择一个执行
* ③ 如果失败(含超时),从列表中移除该Provider
* ④ 重新LoadBalance选择 → 执行
* ⑤ 最多重试 retries 次
*
* 重要约束:必须用于幂等操作!
*/
public class FailoverClusterInvokerSimulation<T> extends AbstractClusterInvoker<T> {
@Override
public Result doInvoke(Invocation invocation,
List<Invoker<T>> invokers,
LoadBalance loadbalance) throws RpcException {
// ===== 1. 参数准备 =====
String methodName = invocation.getMethodName();
int retries = getUrl().getMethodParameter(
methodName, Constants.RETRIES_KEY, 2); // 默认重试次数=2
if (invokers.size() == 1) {
retries = 0; // 只有一个Provider不重试
}
// ===== 2. 保存原始Invoker列表 =====
List<Invoker<T>> invoked = new ArrayList<>(invokers.size());
// ===== 3. 主轮询 =====
RpcException lastException = null;
for (int i = 0; i <= retries; i++) {
Invoker<T> invoker = select(invokers, invocation, loadbalance);
invoked.add(invoker);
try {
// ===== 执行调用 =====
Result result = invoker.invoke(invocation);
// 历史异常日志(非当前失败)
if (lastException != null) {
logger.warn("Retry invoke successfully after {} tries", i);
}
return result;
} catch (RpcException e) {
lastException = e;
// ===== 从候选列表中排除失败的Invoker =====
// 注意:这里只排除不可重试的异常
if (e.isBiz()) {
throw e; // 业务异常不重试
}
// 从invokers中移除(下一次重试不再选它)
invokers = invokers.stream()
.filter(inv -> !inv.equals(invoker))
.collect(Collectors.toList());
} catch (Throwable t) {
lastException = new RpcException(
"Failover invoke error", t);
}
}
// ===== 4. 超出重试次数 → 抛出最终的异常 =====
throw new RpcException(
"Failed to invoke after " + retries + " retries, " +
"last error: " + lastException.getMessage(), lastException);
}
}
3. 其他五种策略快速对比
java
/**
* 六种集群容错策略的源码对比
*/
public class AllClusterStrategies {
/**
* ① Failover(默认)------失败自动切换+重试
*
* 重试次数 = retries(默认2次)
* 适用场景:读操作(幂等)
* 不适用场景:写操作(非幂等)
* 重试行为:选择下一台Provider再试
*/
/**
* ② Failfast ------ 只调用一次,失败立即报错
*
* 仅选择一台Provider执行一次
* 不重试、不切换
* 适用场景:非幂等的写操作
*/
public static class FailfastInvoker extends AbstractClusterInvoker {
@Override
public Result doInvoke(Invocation invocation, List<Invoker<?>> invokers,
LoadBalance lb) {
Invoker<?> invoker = select(invokers, invocation, lb);
try {
return invoker.invoke(invocation);
} catch (Throwable e) {
throw new RpcException("Failfast invoke error", e);
}
}
}
/**
* ③ Failsafe ------ 失败静默处理
*
* 调用失败只记录日志,不抛异常
* 适用场景:审计日志、不重要通知
*/
public static class FailsafeInvoker extends AbstractClusterInvoker {
@Override
public Result doInvoke(Invocation invocation, List<Invoker<?>> invokers,
LoadBalance lb) {
try {
Invoker<?> invoker = select(invokers, invocation, lb);
return invoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failsafe ignore exception", e);
return new RpcResult(); // 返回空Result
}
}
}
/**
* ④ Failback ------ 失败后异步定时重试
*
* 失败调用加入ConcurrentHashMap重试队列
* 后台定时器每5秒重试一次
* 适用场景:消息推送、最终一致性
*/
public static class FailbackInvoker extends AbstractClusterInvoker {
// 重试队列:请求编号 → 原始Invocation
private final ConcurrentMap<Invocation, Invoker<?>> failed =
new ConcurrentHashMap<>();
// 定时重试任务
private static final ScheduledExecutorService RETRY_SCHEDULER =
Executors.newSingleThreadScheduledExecutor();
public FailbackInvoker() {
// 每5秒执行一次重试
RETRY_SCHEDULER.scheduleWithFixedDelay(() -> {
failed.forEach((invocation, invoker) -> {
try {
invoker.invoke(invocation);
failed.remove(invocation); // 成功 → 移除
} catch (Throwable e) {
// 继续留在失败队列
logger.error("Failback retry error", e);
}
});
}, 5, 5, TimeUnit.SECONDS);
}
@Override
protected Result doInvoke(Invocation invocation,
List<Invoker<?>> invokers,
LoadBalance lb) {
try {
Invoker<?> invoker = select(invokers, invocation, lb);
return invoker.invoke(invocation);
} catch (Throwable e) {
// 加入失败重试队列
Invoker<?> selected = select(invokers, invocation, lb);
failed.put(invocation, selected);
return new RpcResult(); // 返回空
}
}
}
/**
* ⑤ Forking ------ 并行调用多个Provider,取最快响应
*
* 适用场景:实时读操作(延迟敏感)
* 成本:每个请求的上游资源开销 = forks × 1
*
* 参数:forks(默认2,建议≤3)
*/
public static class ForkingInvoker extends AbstractClusterInvoker {
@Override
protected Result doInvoke(Invocation invocation,
List<Invoker<?>> invokers,
LoadBalance lb) {
int forks = Math.min(3, invokers.size());
// 线程池并发调用
ExecutorService executor = Executors.newFixedThreadPool(forks);
final BlockingQueue<Object> resultQueue = new LinkedBlockingQueue<>(1);
// 选择forks个Invoker并行执行
List<Invoker<?>> selected = selectMulti(invokers, forks);
for (Invoker<?> invoker : selected) {
executor.submit(() -> {
try {
Result result = invoker.invoke(invocation);
resultQueue.offer(result.defaultValue());
} catch (Throwable e) {
// 忽略失败的
}
});
}
try {
// 等待最快的响应
Object result = resultQueue.poll(timeout, TimeUnit.MILLISECONDS);
return new RpcResult(result);
} catch (InterruptedException e) {
throw new RpcException("Forking invoke interrupted");
}
}
}
/**
* ⑥ Broadcast ------ 逐一遍历所有Provider
*
* 适用场景:缓存刷新、配置更新通知
* 注意:不是并行!是逐一调用
* 任意一台失败 → 立即失败
*/
public static class BroadcastInvoker extends AbstractClusterInvoker {
@Override
protected Result doInvoke(Invocation invocation,
List<Invoker<?>> invokers,
LoadBalance lb) {
RpcException lastException = null;
Result lastResult = null;
for (Invoker<?> invoker : invokers) {
try {
lastResult = invoker.invoke(invocation);
} catch (RpcException e) {
lastException = e;
// 任意一台失败不完全退出(旧版本逻辑)
// 新版本(2.7+)任意失败应立即退出
throw e;
}
}
if (lastException != null) {
throw lastException;
}
return lastResult;
}
}
}
4. 策略选择决策表
java
/**
* 集群容错策略选择指南
*/
public class ClusterStrategySelector {
/**
* 根据业务特征自动推荐集群容错策略
*/
public static String recommend(ServiceFeature feature) {
if (feature.isWriteOperation()) {
// 写操作------不能重试
return "failfast";
}
if (feature.isRealtimeSensitive()) {
// 实时性要求------并行取最快
return "forking";
}
if (feature.isAuxiliaryService()) {
// 辅助服务------失败不影响
return "failsafe";
}
if (feature.isEventuallyConsistent()) {
// 最终一致性------异步重试
return "failback";
}
if (feature.isBroadcastNeeded()) {
// 通知全集群------逐一广播
return "broadcast";
}
// 默认:可重试的读操作
return "failover";
}
static class ServiceFeature {
boolean isWriteOperation() { return false; }
boolean isRealtimeSensitive() { return false; }
boolean isAuxiliaryService() { return false; }
boolean isEventuallyConsistent() { return false; }
boolean isBroadcastNeeded() { return false; }
}
}
本章总结
| 策略 | 重试 | 适用对象 | 关键风险 |
|---|---|---|---|
| Failover | 自动切换+重试 | 幂等读操作 | 非幂等+重试=重复执行 |
| Failfast | 只一次 | 非幂等写操作 | 调用方必须处理异常 |
| Failsafe | 否(静默) | 日志/审计/不重要 | 调用方感知不到失败 |
| Failback | 异步定时 | 通知/消息推送 | 重启后重试丢失 |
| Forking | 并行 | 实时读操作 | 资源消耗×N倍 |
| Broadcast | 逐一 | 缓存刷新/通知 | 全部成功 |