17 Dubbo 2.7 集群容错策略源码解析

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 逐一 缓存刷新/通知 全部成功
相关推荐
Refrain_zc1 小时前
Android开发纯按键文件浏览器
java
monkeyhlj1 小时前
Harness理解学习
java·人工智能·python·学习·ai编程
Oneslide1 小时前
银河麒麟紧Linux 内核CopyFail高危漏洞(CVE-2026-31431)通告
后端
RelishCoding1 小时前
SpringMVC-01
后端·spring
未若君雅裁2 小时前
Spring Cloud 组件全景与注册中心原理
后端·spring·spring cloud
西凉的悲伤2 小时前
SpringBoot WebClient 介绍
java·spring boot·后端·webclient
Simon523142 小时前
mybatis执行流程、关联映射、注解开发
java·开发语言·mybatis
神奇小汤圆2 小时前
从零搭建量化投资系统:用 Qlib 一行代码搞定均线分析
后端
冷雨夜中漫步2 小时前
SQLite 深度解析:在 Java/Spring 中的使用与H2、Derby对比
java·spring·sqlite