前言
在微服務架構中,服務間的依賴關係複雜且動態,任何一個服務的故障都可能引發連鎖反應,導致系統雪崩。一個好的容錯設計可以避免這些問題發生:
- 服務雪崩效應:單個服務崩潰或響應延遲可能導致調用鏈上的所有服務被阻塞,最終拖垮整個系統。例如,若服務 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(失敗自動切換,默認策略)
實現原理:通過循環重試實現容錯。
實現源碼關鍵點:
- FailoverClusterInvoker 的 doInvoke 方法中,通過 for 循環控制重試次數(默認重試 2 次,共調用 3 次);
- 每次重試前調用 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(快速失敗)
實現原理:僅發起一次調用,異常直接拋出。
實現源碼關鍵點:
- 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(失敗安全)
實現原理:異常被捕獲後返回空結果,不中斷流程。
實現源碼關鍵點:
- 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(失敗自動恢復)
實現原理:失敗請求存入隊列,定時重試。
實現源碼關鍵點:
- 捕獲失敗異常,使用 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(並行調用)
實現原理:併發調用多個節點,首個成功結果即返回。
實現源碼關鍵點:
- 使用線程池併發調用,結果通過 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(廣播調用)
實現原理:逐個調用所有節點,任一失敗則整體失敗。
實現源碼關鍵點:
- 遍歷所有 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 的集羣容錯實現中有哪些優秀設計值得我們學習?