轉載:https://blog.csdn.net/Revivedsun/article/details/72851417
重試的實作
當消費端發起一次調用,如果叢集容錯模式選擇的是FailoverCluster模式(預設模式),當調用發生失敗會自動發起切換,重試其它伺服器。
<dubbo:reference>
<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
FailoverCluster模式的實作是在
com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker<T>
該類的實作如下,最終的調用是通過doInvoke方法完成。
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class);
public FailoverClusterInvoker(Directory<T> directory) {
super(directory);
}
@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);
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + ;
if (len <= ) {
len = ;
}
// retry loop.
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 = ; i < len; i++) {
//重試時,進行重新選擇,避免重試時invoker清單已發生變化.
//注意:如果清單發生了變化,那麼invoked判斷會失效,因為invoker示例已經改變
if (i > ) {
checkWheatherDestoried();
copyinvokers = list(invocation);
//重新檢查一下
checkInvokers(copyinvokers, invocation);
}
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List)invoked);
try {
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le != null ? le.getCode() : , "Failed to invoke the method "
+ invocation.getMethodName() + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
}
}
首先擷取使用者配置的重試次數,如果未配置,則預設為1。
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + ;
if (len <= ) {
len = ;
}
接着通過循環完成重試調用。調用Result result = invoker.invoke(invocation);傳回com.alibaba.dubbo.rpc.RpcResult,這個類内部封裝了傳回結果,抛出的異常,以及附件(attachments)資訊。
private Object result;
private Throwable exception;
private Map<String, String> attachments = new HashMap<String, String>();
如果進行調用,服務方抛出異常,那麼異常會記錄在Result的exception成員中,如果調用時消費端檢測到逾時或網絡異常等那麼消費端将會抛出異常,并在如下代碼中進行重試。
for (int i = ; i < len; i++) {
......
......
try {
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
......
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le != null ? le.getCode() : , "Failed to invoke the method "
+ invocation.getMethodName() + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
當産生異常,且異常code為BIZ_EXCEPTION,那麼也不會進行重試。
com.alibaba.dubbo.rpc.RpcException定義的異常Code有如下幾種類型。
public static final int UNKNOWN_EXCEPTION = ;
public static final int NETWORK_EXCEPTION = ;
public static final int TIMEOUT_EXCEPTION = ;
public static final int BIZ_EXCEPTION = ;
public static final int FORBIDDEN_EXCEPTION = ;
public static final int SERIALIZATION_EXCEPTION = ;
通過檢視RpcException的setCode調用關系可以看到産BIZ_EXCEPTION的情況。
最後重試實作可總結為:通過循環重複調用方法,如果調用得到響應,則正常傳回,産生的異常作為結果的成員。如果得不到響應如逾時,網絡異常,序列化失敗等問題則去嘗試重試。
逾時的判斷
在重試的實作中可以看到,進行方法調用最終是調用invoker的doInvoke方法完成最終調用。
例如:
com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(Invocation)
方法内部送出請求後通過future的get方法擷取結果。
return (Result) currentClient.request(inv, timeout).get();
get記錄開始時間start,然後不斷循環檢查是否得到了傳回結果,如果在設定的逾時時間内沒有傳回結果,那麼跳出循環,并抛出逾時異常。如果在指定的逾時時間内得到響應則傳回結果。
public Object get(int timeout) throws RemotingException {
if (timeout <= ) {
timeout = Constants.DEFAULT_TIMEOUT;
}
if (! isDone()) {
long start = System.currentTimeMillis();
lock.lock();
try {
while (! isDone()) {
done.await(timeout, TimeUnit.MILLISECONDS);
if (isDone() || System.currentTimeMillis() - start > timeout) {
break;
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
if (! isDone()) {
throw new TimeoutException(sent > , channel, getTimeoutMessage(false));
}
}
return returnFromResponse();
}
通過逾時判定實作我們發現,當一個任務處理時間很久,而消費端配置的逾時時間又很短就會出現消費端産生逾時異常,而服務提供方卻成功完成了操作的現象。是以對服務提供方的處理時間做出規劃,配置合理的逾時時間,或通過回調方法傳回結果給消費端。這樣避免消費端發生逾時異常,而服務提供方處理完成的問題。
參考
dubbo 2.8.4 源碼
轉載:https://blog.csdn.net/angry_tiger/article/details/51073459
Dubbo的逾時重試機制為服務容錯、服務穩定提供了比較好的架構支援,但是在一些比較特殊的網絡環境下(網絡傳輸慢,并發多)可能
由于服務響應慢,Dubbo自身的逾時重試機制(服務端的處理時間超過了設定的逾時時間時,就會有重複請求)可能會帶來一些麻煩。
常見的應用場景故障: 1、發送郵件(重複) ;2、賬戶注冊(重複).。
解決方案:
1.對于核心的服務中心,去除dubbo逾時重試機制,并重新評估設定逾時時間。
(1)、去掉逾時重試機制
<dubbo:provider delay="-1" timeout="6000" retries="0"/>
(2)、重新評估設定逾時時間
<dubbo:service inter ref="*" timeout="延長服務時間"/>
2.業務處理代碼必須放在服務端,用戶端隻做參數驗證和服務調用,不涉及業務流程處理。