Cluster層
在維服務環境中,爲了保證服務的高可用,服務通常都是以集羣方式出現,很少出現單點服務。服務不是每時每刻都保持良好運行,如網絡抖動、服務短暫不可用等,需要集羣進行自動容錯。在本地測試、服務降級,則需要Mock返回結果
通過Cluster的擴展點實現容錯機制
mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster
forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
zone-aware=org.apache.dubbo.rpc.cluster.support.registry.ZoneAwareCluster
- Failover 失敗自動切換重試,可用通過
retries="2"
設置重試次數。這是dubbo的默認容錯機制,會對請求做負載均衡。通常使用在讀操作或冥等的寫操作上,但重試會導致接口的延遲增長,在下游機器負載達到極限時,重試容易加重下游服務的負載 - Failfast 快速失敗。當請求失敗後,快速返回異常結果,不做任何重試。該容錯機制會對請求做負載均衡,通常用在非冥等接口的調用上。受網絡抖動的影響比較大
- Failsafe 失敗安全,當出現異常時,直接忽略異常,會對請求做負載均衡。通常使用在”佛系"調用場景,即不關心調用是否成功,並且不想拋出異常影響外層調用,通常用在如不重要的日誌同步,即使出現異常也無所謂
- Failback 失敗自動恢復,請求失敗後,會自動記錄在失敗隊列中,並由一個定時線程定時重試,適用於一些異步或最終一致性的請求。請求會做負載均衡
- Forking 並行調用多個服務提供者,只要其中一個返回,則立即返回結果。用戶可以配置forks=“最大並行調用數"參數來確定最大並行調用的服務數量。通常使用在對接口實時性要求極高的調用上,但會浪費更多的資源
- Broadcast 廣播調用所有的服務,任意一個節點報錯則報錯。由於是廣播,因此請求不需要做負載均衡。通常用戶服務狀態更新後的廣播
- Mock 廣播調用所有可用的服務,任意一個節點報錯則報錯
- Available 最簡單的方式,請求不會做負載均衡,遍歷所有的服務列表,找到第一個可用的節點,直接請求並返回結果。如果沒有可用的節點,則直接跑出異常
- Mergeable 可以自動把多個節點請求得到的結果進行合併
AbstractClusterInvoker
public Result invoke(final Invocation invocation) throws RpcException {
checkWhetherDestroyed();
// 綁定attachments 到 invocation中
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// 列舉 Invoker
List<Invoker<T>> invokers = list(invocation);
// 加載 loadBalance
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
// 調用 doInvoke進行後續
return doInvoke(invocation, invokers, loadbalance);
}
// 由容錯策略實現的
protected abstract Result doInvoke(Invocation invocation, List<Invoker<T>> invokers,
LoadBalance loadbalance) throws RpcException;
protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
// 獲取調用方法名
String methodName = invocation == null ? StringUtils.EMPTY_STRING : invocation.getMethodName();
// 獲取 sticky 配置,sticky 表示粘滯連接。所謂粘滯連接是指讓服務消費者儘可能的
// 調用同一個服務提供者,除非該提供者掛了再進行切換
boolean sticky = invokers.get(0).getUrl()
.getMethodParameter(methodName, CLUSTER_STICKY_KEY, DEFAULT_CLUSTER_STICKY);
// 檢測 invokers 列表是否包含 stickyInvoker,如果不包含,
// 說明 stickyInvoker 代表的服務提供者掛了,此時需要將其置空
if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
stickyInvoker = null;
}
// 在 sticky 爲 true,且 stickyInvoker != null 的情況下。如果 selected 包含
// stickyInvoker,表明 stickyInvoker 對應的服務提供者可能因網絡原因未能成功提供服務。
// 但是該提供者並沒掛,此時 invokers 列表中仍存在該服務提供者對應的 Invoker。
if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
// availablecheck 表示是否開啓了可用性檢查,如果開啓了,則調用 stickyInvoker 的
// isAvailable 方法進行檢查,如果檢查通過,則直接返回 stickyInvoker。
if (availablecheck && stickyInvoker.isAvailable()) {
return stickyInvoker;
}
}
// availablecheck 表示是否開啓了可用性檢查,如果開啓了,則調用 stickyInvoker 的
// isAvailable 方法進行檢查,如果檢查通過,則直接返回 stickyInvoker。
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
// 如果 sticky 爲 true,則將負載均衡組件選出的 Invoker 賦值給 stickyInvoker
if (sticky) {
stickyInvoker = invoker;
}
return invoker;
}
select 方法的主要邏輯集中在了對粘滯連接特性的支持上。首先是獲取 sticky 配置,然後再檢測 invokers 列表中是否包含 stickyInvoker,如果不包含,則認爲該 stickyInvoker 不可用,此時將其置空。這裏的 invokers 列表可以看做是存活着的服務提供者列表,如果這個列表不包含 stickyInvoker,那自然而然的認爲 stickyInvoker 掛了,所以置空。如果 stickyInvoker 存在於 invokers 列表中,此時要進行下一項檢測 — 檢測 selected 中是否包含 stickyInvoker。如果包含的話,說明 stickyInvoker 在此之前沒有成功提供服務(但其仍然處於存活狀態)。此時我們認爲這個服務不可靠,不應該在重試期間內再次被調用,因此這個時候不會返回該 stickyInvoker。如果 selected 不包含 stickyInvoker,此時還需要進行可用性檢測,比如檢測服務提供者網絡連通性等。當可用性檢測通過,纔可返回 stickyInvoker,否則調用 doSelect 方法選擇 Invoker。如果 sticky 爲 true,此時會將 doSelect 方法選出的 Invoker 賦值給 stickyInvoker
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation,
List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
if (invokers.size() == 1) {
return invokers.get(0);
}
// 通過負載均衡 選擇Invoker
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
// 如果selected 包含負載均衡選擇出Invoker,或者該Invoker無法經過可用性檢查,此時進行重選
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
// 進行重選
Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rInvoker != null) {
invoker = rInvoker;
} else {
// 如果 rinvoker 不爲空,則將其賦值給 invoker
int index = invokers.indexOf(invoker);
try {
// 獲取 index + 1 位置處的 Invoker,以下代碼等價於:
// invoker = invokers.get((index + 1) % invokers.size());
invoker = invokers.get((index + 1) % invokers.size());
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error(...);
}
}
return invoker;
}
FailoverClusterInvoker
FailoverClusterInvoker 在調用失敗時,會自動切換 Invoker 進行重試。默認配置下,Dubbo 會使用這個類作爲缺省 Cluster Invoker
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyInvokers = invokers;
checkInvokers(copyInvokers, invocation);
String methodName = RpcUtils.getMethodName(invocation);
// 獲取重試次數
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
RpcException le = null; // last exception.
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++) {
//Reselect before retry to avoid a change of candidate `invokers`.
//NOTE: if `invokers` changed, then `invoked` also lose accuracy.
if (i > 0) {
checkWhetherDestroyed();
// 在進行重試前重新列舉Invoker,這些的好處是,如果某個服務掛了
// 通過調用list可得到最新可用的Invoker列表
copyInvokers = list(invocation);
// 重建檢查
checkInvokers(copyInvokers, invocation);
}
// 通過負載均衡選擇Invoker
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
// 添加到invoker到invoked列表中,記錄已經調用的invoker 失敗自動切換
invoked.add(invoker);
// 設置invoked 到 RPC 上下文中
RpcContext.getContext().setInvokers((List) invoked);
try {
// 調用目標Invoker的invoke方法
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn(...);
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // 業務異常,直接拋出
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
// 若重試失敗 ,則拋出異常
throw new RpcException(...);
}
FailfastClusterInvoker
FailfastClusterInvoker 只會進行一次調用,失敗後立即拋出異常。適用於冪等操作,比如新增記錄。
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
checkInvokers(invokers, invocation);
// 選擇Invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
try {
// 調用Invoker
return invoker.invoke(invocation);
} catch (Throwable e) {
if (e instanceof RpcException && ((RpcException) e).isBiz()) { // biz exception.
throw (RpcException) e;
}
// 失敗,直接拋出異常
throw new RpcException(...);
}
}
FailsafeClusterInvoker
FailsafeClusterInvoker 是一種失敗安全的 Cluster Invoker。所謂的失敗安全是指,當調用過程中出現異常時,FailsafeClusterInvoker 僅會打印異常,而不會拋出異常。適用於寫入審計日誌等操作
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
checkInvokers(invokers, invocation);
// 選擇 Invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
// 進行遠程調用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 打印錯誤日誌,但不拋出
logger.error("Failsafe ignore exception: " + e.getMessage(), e);
// 返回空結果忽略錯誤
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
FailbackClusterInvoker
FailbackClusterInvoker
會在調用失敗後,返回一個空結果給服務消費者。並通過定時任務對失敗的調用進行重傳,適合執行消息通知等操作
public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final long RETRY_FAILED_PERIOD = 5; // 默認衰減重試5次
private final int retries; // 重試次數
private final int failbackTasks;
private volatile Timer failTimer; // 定時任務,對失敗重新調用 衰減重試
public FailbackClusterInvoker(Directory<T> directory) {
super(directory);
int retriesConfig = getUrl().getParameter(RETRIES_KEY, DEFAULT_FAILBACK_TIMES);
if (retriesConfig <= 0) {
retriesConfig = DEFAULT_FAILBACK_TIMES;
}
int failbackTasksConfig = getUrl().getParameter(FAIL_BACK_TASKS_KEY, DEFAULT_FAILBACK_TASKS);
if (failbackTasksConfig <= 0) {
failbackTasksConfig = DEFAULT_FAILBACK_TASKS;
}
retries = retriesConfig;
failbackTasks = failbackTasksConfig;
}
private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {
if (failTimer == null) {
synchronized (this) {
if (failTimer == null) {
// HashedWheelTimer dubbo的時間輪方式
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(...);
}
}
@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
Invoker<T> invoker = null;
try {
checkInvokers(invokers, invocation);
// 選擇Invoker
invoker = select(loadbalance, invocation, invokers, null);
// 進行調用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 如果調用過程中發生異常,此時僅打印錯誤日誌,不拋出異常
logger.error(...);
// 記錄調用信息
addFailed(loadbalance, invocation, invokers, invoker);
// 返回一個空結果給服務消費者
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
}
ForkingClusterInvoker
ForkingClusterInvoker 會在運行時通過線程池創建多個線程,併發調用多個服務提供者。只要有一個服務提供者成功返回了結果,doInvoke 方法就會立即結束運行。ForkingClusterInvoker 的應用場景是在一些對實時性要求比較高讀操作(注意是讀操作,並行寫操作可能不安全)下使用,但這將會耗費更多的資源
private final ExecutorService executor = Executors.newCachedThreadPool(
new NamedInternalThreadFactory("forking-cluster-timer", true));
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
checkInvokers(invokers, invocation);
final List<Invoker<T>> selected;
// 獲取forks配置
final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS);
// 獲取超時時間
final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
// 如果forks配置,直接將invokers賦值給selected
if (forks <= 0 || forks >= invokers.size()) {
selected = invokers;
} else {
// 循環選出 forks個Invoker,並添加到selected中
selected = new ArrayList<>();
for (int i = 0; i < forks; i++) {
// 選出Invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
if (!selected.contains(invoker)) {
selected.add(invoker);
}
}
}
RpcContext.getContext().setInvokers((List) selected);
final AtomicInteger count = new AtomicInteger();
// 阻塞隊列
final BlockingQueue<Object> ref = new LinkedBlockingQueue<>();
// 遍歷selected列表
for (final Invoker<T> invoker : selected) {
// 爲每個Invoker創建一個執行線程
executor.execute(() -> {
try {
// 進行遠程調用
Result result = invoker.invoke(invocation);
// 將結果添加到阻塞隊列中
ref.offer(result);
} catch (Throwable e) {
int value = count.incrementAndGet();
// 僅在value大於等於select.size()時,纔將異常
// 放入阻塞隊列中 --> 都調不通才拋異常
if (value >= selected.size()) {
ref.offer(e);
}
}
});
}
try {
// 從阻塞隊列中取出遠程調用結果
Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
// 如果結果類型爲Trowable,則拋出異常
if (ret instanceof Throwable) {
Throwable e = (Throwable) ret;
throw new RpcException(...);
}
// 返回結果
return (Result) ret;
} catch (InterruptedException e) {
throw new RpcException(...);
}
} finally {
// clear attachments which is binding to current thread.
RpcContext.getContext().clearAttachments();
}
}
BroadcastClusterInvokerBroadcastClusterInvoker
BroadcastClusterInvoker 會逐個調用每個服務提供者,如果其中一臺報錯,在循環調用結束後,BroadcastClusterInvoker 會拋出異常。該類通常用於通知所有提供者更新緩存或日誌等本地資源信息
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
checkInvokers(invokers, invocation);
RpcContext.getContext().setInvokers((List) invokers);
RpcException exception = null;
Result result = null;
// 遍歷Invoker列表,逐個調用
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;
}
AvailableClusterInvoker
找到第一個可用的服務直接調用,並返回結果
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 遍歷invokers列表,如果有Invoker是可用的,直接調用並返回
for (Invoker<T> invoker : invokers) {
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
// 遍歷列表未找到可用的Invoker,拋出異常
throw new RpcException("No provider available in " + invokers);
}
ClusterInterceptor
集羣調用的攔截器
META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.interceptor.ClusterInterceptor
擴展點
context=org.apache.dubbo.rpc.cluster.interceptor.ConsumerContextClusterInterceptor
zone-aware=org.apache.dubbo.rpc.cluster.interceptor.ZoneAwareClusterInterceptor
ClusterInterceptor
擴展點接口
@SPI
public interface ClusterInterceptor {
void before(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation);
void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation);
default Result intercept(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) throws RpcException {
return clusterInvoker.invoke(invocation);
}
interface Listener {
void onMessage(Result appResponse, AbstractClusterInvoker<?> clusterInvoker, Invocation invocation);
void onError(Throwable t, AbstractClusterInvoker<?> clusterInvoker, Invocation invocation);
}
}
實現攔截邏輯
public abstract class AbstractCluster implements Cluster {
// 構建攔截器鏈路
private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
AbstractClusterInvoker<T> last = clusterInvoker;
// 通過擴展點加載所有的Interceptor
List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
if (!interceptors.isEmpty()) {
for (int i = interceptors.size() - 1; i >= 0; i--) {
final ClusterInterceptor interceptor = interceptors.get(i);
final AbstractClusterInvoker<T> next = last;
last = new InterceptorInvokerNode<>(clusterInvoker, interceptor, next);
}
}
return last;
}
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result asyncResult;
try {
interceptor.before(next, invocation);
asyncResult = interceptor.intercept(next, invocation);
} catch (Exception e) {
// onError callback
if (interceptor instanceof ClusterInterceptor.Listener) {
ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
listener.onError(e, clusterInvoker, invocation);
}
throw e;
} finally {
interceptor.after(next, invocation);
}
return asyncResult.whenCompleteWithContext((r, t) -> {
// onResponse callback
if (interceptor instanceof ClusterInterceptor.Listener) {
ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
if (t == null) {
listener.onMessage(r, clusterInvoker, invocation);
} else {
listener.onError(t, clusterInvoker, invocation);
}
}
});
}
@Override
public void destroy() {
clusterInvoker.destroy();
}
@Override
public String toString() {
return clusterInvoker.toString();
}
@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
return null;
}
}
ConsumerContextClusterInterceptor
消費端集羣調用,處理上下文的攔截器
public class ConsumerContextClusterInterceptor implements ClusterInterceptor, ClusterInterceptor.Listener {
@Override
public void before(AbstractClusterInvoker<?> invoker, Invocation invocation) {
RpcContext.getContext()
.setInvocation(invocation)
.setLocalAddress(NetUtils.getHostAddress(), 0);
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(invoker);
}
RpcContext.removeServerContext();
}
@Override
public void after(AbstractClusterInvoker<?> clusterInvoker, Invocation invocation) {
RpcContext.removeContext();
}
@Override
public void onMessage(Result appResponse, AbstractClusterInvoker<?> invoker, Invocation invocation) {
RpcContext.getServerContext().setAttachments(appResponse.getAttachments());
}
@Override
public void onError(Throwable t, AbstractClusterInvoker<?> invoker, Invocation invocation) {
}
}