博客 / 詳情

返回

Dubbo 中的集羣容錯

前言

在微服務架構中,服務間的依賴關係複雜且動態,任何一個服務的故障都可能引發連鎖反應,導致系統雪崩。一個好的容錯設計可以避免這些問題發生:

  • 服務雪崩效應:單個服務崩潰或響應延遲可能導致調用鏈上的所有服務被阻塞,最終拖垮整個系統。例如,若服務 A 依賴服務 B,而服務 B 因高負載無法響應,A 的線程池可能被佔滿,進而影響其他依賴A的服務;
  • 分佈式系統的脆弱性:網絡抖動、節點宕機、資源耗盡等問題在分佈式環境中不可避免。容錯機制通過冗餘和快速失敗策略,確保部分故障不會擴散到整個系統;
  • 服務可用性低:微服務的目標是提升系統可用性,而容錯設計(如故障轉移、熔斷)是保障服務持續可用的核心手段。例如,通過自動切換健康節點,避免單點故障。

Dubbo 的集羣容錯機制

在 Dubbo 中,多個 Provider 實例構成一個「集羣」。消費者調用時,Dubbo 通過 Cluster 模塊實現容錯策略的封裝和路由,Cluster 模塊會根據配置(如 cluster=failover)裝配不同的容錯策略實現類,對 Directory 中的多個 Invoker 進行處理,返回一個可執行的 Invoker。Dubbo 當前已支持以下 6 種容錯策略(在 org.apache.dubbo.rpc.cluster.support 包下):

策略簡稱 實現類名 特性 使用場景
Failover FailoverClusterInvoker 失敗自動重試,默認實現 網絡不穩定,民登操作
Failfast FailfastClusterInvoker 快速失敗,不重試 響應時間敏感,非冪等
Failsafe FailsafeClusterInvoker 失敗忽略異常 日誌記錄、監控等非主要場景
Failback FailbackClusterInvoker 失敗後後台重試 可容忍失敗,後續補償重試
Forking ForkingClusterInvoker 並行調用多個節點,最快成功返回 實時性要求高,資源充足
Broadcast BroadcastClusterInvoker 廣播方式調用所有服務提供着 配置更新、通知類等操作

Failover Cluster(失敗自動切換,默認策略)

實現原理:通過循環重試實現容錯。
實現源碼關鍵點:

  1. FailoverClusterInvoker 的 doInvoke 方法中,通過 for 循環控制重試次數(默認重試 2 次,共調用 3 次);
  2. 每次重試前調用 list(invocation) 重新獲取最新的 Invoker 列表,確保動態感知節點變化。
// 代碼片段:org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke
for (int i = 0; i < len; i++) {
    if (i > 0) {
        copyInvokers = list(invocation); // 動態刷新 Invoker 列表
    }
    Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
    // 調用並處理異常...
}

Failfast Cluster(快速失敗)

實現原理:僅發起一次調用,異常直接拋出。
實現源碼關鍵點:

  1. FailfastClusterInvoker 直接調用目標 Invoker,不進行重試。
// 代碼片段:org.apache.dubbo.rpc.cluster.support.FailfastClusterInvoker#doInvoke
fpublic Result doInvoke(...) throws RpcException {
    checkInvokers(invokers, invocation);
    Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
    return invoker.invoke(invocation); // 僅一次調用
}

Failsafe Cluster(失敗安全)

實現原理:異常被捕獲後返回空結果,不中斷流程。
實現源碼關鍵點:

  1. ailsafeClusterInvoker通過try-catch捕獲異常並記錄日誌。
// 代碼片段:org.apache.dubbo.rpc.cluster.support.FailsafeClusterInvoker
try {
    // 調用邏輯...
} catch (Throwable e) {
    logger.error("Failsafe ignore exception", e);
    return new RpcResult(); // 返回空結果
}

Failback Cluster(失敗自動恢復)

實現原理:失敗請求存入隊列,定時重試。
實現源碼關鍵點:

  1. 捕獲失敗異常,使用 RetryTimerTask 存儲失敗請求,定時觸發重試。
// 代碼片段:org.apache.dubbo.rpc.cluster.support.FailbackClusterInvoker#doInvoke
private void addFailed(
        LoadBalance loadbalance,
        Invocation invocation,
        List<Invoker<T>> invokers,
        Invoker<T> lastInvoker,
        URL consumerUrl) {
    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, consumerUrl);
    try {
        failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
    } catch (Throwable e) {
        logger.error(
                CLUSTER_TIMER_RETRY_FAILED,
                "add newTimeout exception",
                "",
                "Failback background works error, invocation->" + invocation + ", exception: " + e.getMessage(),
                e);
    }
}

Forking Cluster(並行調用)

實現原理:併發調用多個節點,首個成功結果即返回。
實現源碼關鍵點:

  1. 使用線程池併發調用,結果通過 BlockingQueue 異步接收。
// 代碼片段:org.apache.dubbo.rpc.cluster.support.ForkingClusterInvoker#doInvoke
for (Invoker<T> invoker : selected) {
    executor.execute(() -> {
        Result result = invoker.invoke(invocation);
        ref.offer(result); // 結果存入隊列
    });
}

Broadcast Cluster(廣播調用)

實現原理:逐個調用所有節點,任一失敗則整體失敗。
實現源碼關鍵點:

  1. 遍歷所有 Invoker 調用,異常累積後拋出。
// 代碼片段:org.apache.dubbo.rpc.cluster.support.BroadcastClusterInvoker#doInvoke
for (Invoker<T> invoker : invokers) {
    try {
        invoker.invoke(invocation);
    } catch (RpcException e) {
        exception = e;
    }
}
if (exception != null) throw exception;

如何自定義集羣容錯策略

如果以上提供的容錯策略不滿足需求,Dubbo 支持通過 SPI 自定義 Cluster 實現,步驟如下:

第一步:實現 Cluster 和 AbstractClusterInvoker
@SPI("custom")
public class MyCluster implements Cluster {

    @Override
    public <T> Invoker<T> join(Directory<T> directory) {
        return new MyClusterInvoker<>(directory);
    }

}
public class MyClusterInvoker<T> extends AbstractClusterInvoker<T> {

    @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) {
        // 自定義邏輯,例如條件重試、動態路由等
    }

}
第二步:添加 SPI 配置

META-INF/dubbo/org.apache.dubbo.rpc.cluster.Cluster 中添加配置:

mycluster=com.example.MyCluster
第三步:配置使用自定義容錯策略
<dubbo:reference cluster="mycluster" />

總結

建議核心服務優先使用 Failover(失敗自動切換) 策略保障可用性,非核心服務可降級為 Failsafe(失敗安全)。同時結合 Hystrix(已停止更新) 或 Sentinel 實現熔斷與限流,增強容錯能力。

通過靈活組合 Dubbo 的容錯策略,可顯著提升分佈式系統的魯棒性。實際應用配置時需要根據業務特性權衡延遲、資源開銷與一致性要求,一切皆是 trade off ~

P.S. 不妨再深入思考一下:Dubbo 的集羣容錯實現中有哪些優秀設計值得我們學習?

user avatar FatTiger4399 頭像 prepared 頭像 pudongping 頭像 fedl 頭像 ximinghui 頭像 codingembedded 頭像 duokeli 頭像 emanjusaka 頭像 codelogs 頭像 chuck1sn 頭像 tigerb 頭像
11 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.