dubbo超时重试和异常处理
dubbo超時重試和異常處理
?
參考:
https://www.cnblogs.com/ASPNET2008/p/7292472.html
https://www.tuicool.com/articles/YfA3Ub
https://www.cnblogs.com/binyue/p/5380322.html
https://blog.csdn.net/mj158518/article/details/51228649
?
?
dubbo源碼分析:超時原理以及應(yīng)用場景
本篇主要記錄dubbo中關(guān)于超時的常見問題,實現(xiàn)原理,解決的問題以及如何在服務(wù)降級中體現(xiàn)作用等。
超時問題
為了檢查對dubbo超時的理解,嘗試回答如下幾個問題,如果回答不上來或者不確定那么說明此處需要再多研究研究。
我只是針對個人的理解提問題,并不代表我理解的就是全面深入的,但我的問題如果也回答不了,那至少說明理解的確是不夠細的。
- 超時是針對消費端還是服務(wù)端?
- 超時在哪設(shè)置?
- 超時設(shè)置的優(yōu)先級是什么?
- 超時的實現(xiàn)原理是什么?
- 超時解決的是什么問題?
問題解答
RPC場景
本文所有問題均以下圖做為業(yè)務(wù)場景,一個web api做為前端請求,product service是產(chǎn)品服務(wù),其中調(diào)用comment service(評論服務(wù))獲取產(chǎn)品相關(guān)評論,comment service從持久層中加載數(shù)據(jù)。
超時是針對消費端還是服務(wù)端?
-
如果是爭對消費端,那么當(dāng)消費端發(fā)起一次請求后,如果在規(guī)定時間內(nèi)未得到服務(wù)端的響應(yīng)則直接返回超時異常,但服務(wù)端的代碼依然在執(zhí)行。
-
如果是爭取服務(wù)端,那么當(dāng)消費端發(fā)起一次請求后,一直等待服務(wù)端的響應(yīng),服務(wù)端在方法執(zhí)行到指定時間后如果未執(zhí)行完,此時返回一個超時異常給到消費端。
dubbo的超時是爭對客戶端的,由于是一種NIO模式,消費端發(fā)起請求后得到一個ResponseFuture,然后消費端一直輪詢這個ResponseFuture直至超時或者收到服務(wù)端的返回結(jié)果。雖然超時了,但僅僅是消費端不再等待服務(wù)端的反饋并不代表此時服務(wù)端也停止了執(zhí)行。
按上圖的業(yè)務(wù)場景,看看生成的日志:
product service:報超時錯誤,因為comment service 加載數(shù)據(jù)需要5S,但接口只等1S?。
Caused by: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout. start time: 2017-08-05 18:14:52.751, end time: 2017-08-05 18:14:53.764, client elapsed: 6 ms, server elapsed: 1006 ms, timeout: 1000 ms, request: Request [id=0, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=getCommentsByProductId, parameterTypes=[class java.lang.Long], arguments=[1], attachments={traceId=6299543007105572864, spanId=6299543007105572864, input=259, path=com.jim.framework.dubbo.core.service.CommentService, interface=com.jim.framework.dubbo.core.service.CommentService, version=0.0.0}]], channel: /192.168.10.222:53204 -> /192.168.10.222:7777at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:107) ~[dubbo-2.5.3.jar:2.5.3]at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:84) ~[dubbo-2.5.3.jar:2.5.3]at com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:96) ~[dubbo-2.5.3.jar:2.5.3]... 42 common frames omittedcomment service : 并沒有異常,而是慢慢悠悠的執(zhí)行自己的邏輯:
2017-08-05 18:14:52.760 INFO 846 --- [2:7777-thread-5] c.j.f.d.p.service.CommentServiceImpl : getComments start:Sat Aug 05 18:14:52 CST 2017 2017-08-05 18:14:57.760 INFO 846 --- [2:7777-thread-5] c.j.f.d.p.service.CommentServiceImpl : getComments end:Sat Aug 05 18:14:57 CST 2017從日志來看,超時影響的是消費端,與服務(wù)端沒有直接關(guān)系。
超時在哪設(shè)置?
消費端
- 全局控制
- 接口控制
- 方法控制
服務(wù)端
- 全局控制
- 接口控制
- 方法控制
可以看到dubbo針對超時做了比較精細化的支持,無論是消費端還是服務(wù)端,無論是接口級別還是方法級別都有支持。
超時設(shè)置的優(yōu)先級是什么?
上面有提到dubbo支持多種場景下設(shè)置超時時間,也說過超時是針對消費端的。那么既然超時是針對消費端,為什么服務(wù)端也可以設(shè)置超時呢?
這其實是一種策略,其實服務(wù)端的超時配置是消費端的缺省配置,即如果服務(wù)端設(shè)置了超時,任務(wù)消費端可以不設(shè)置超時時間,簡化了配置。
另外針對控制的粒度,dubbo支持了接口級別也支持方法級別,可以根據(jù)不同的實際情況精確控制每個方法的超時時間。所以最終的優(yōu)先順序為:客戶端方法級>服務(wù)端方法級>客戶端接口級>服務(wù)端接口級>客戶端全局>服務(wù)端全局
超時的實現(xiàn)原理是什么?
之前有簡單提到過,?dubbo默認采用了netty做為網(wǎng)絡(luò)組件,它屬于一種NIO的模式。消費端發(fā)起遠程請求后,線程不會阻塞等待服務(wù)端的返回,而是馬上得到一個ResponseFuture,消費端通過不斷的輪詢機制判斷結(jié)果是否有返回。因為是通過輪詢,輪詢有個需要特別注要的就是避免死循環(huán),所以為了解決這個問題就引入了超時機制,只在一定時間范圍內(nèi)做輪詢,如果超時時間就返回超時異常。
源碼
ResponseFuture接口定義
public interface ResponseFuture {/*** get result.* * @return result.*/Object get() throws RemotingException;/*** get result with the specified timeout.* * @param timeoutInMillis timeout.* @return result.*/Object get(int timeoutInMillis) throws RemotingException;/*** set callback.* * @param callback*/void setCallback(ResponseCallback callback);/*** check is done.* * @return done or not.*/boolean isDone();}ReponseFuture的實現(xiàn)類:DefaultFuture
只看它的get方法,可以清楚看到輪詢的機制。
public Object get(int timeout) throws RemotingException {if (timeout <= 0) {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 > 0, channel, getTimeoutMessage(false));}}return returnFromResponse();}超時解決的是什么問題?
設(shè)置超時主要是解決什么問題?如果沒有超時機制會怎么樣?
回答上面的問題,首先要了解dubbo這類rpc產(chǎn)品的線程模型。下圖是我之前個人RPC學(xué)習(xí)產(chǎn)品的示例圖,與dubbo的線程模型大致是相同的,有興趣的可參考我的筆記:簡單RPC框架-業(yè)務(wù)線程池
我們從dubbo的源碼看下這下線程模型是怎么用的:
netty boss
主要是負責(zé)socket連接之類的工作。
netty wokers
將一個請求分給后端的某個handle去處理,比如心跳handle?,執(zhí)行業(yè)務(wù)請求的 handle等。
Netty Server中可以看到上述兩個線程池是如何初始化的:
首選是open方法,可以看到一個boss一個worker線程池。
protected void doOpen() throws Throwable {NettyHelper.setNettyLoggerFactory();ExecutorService boss = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerBoss", true));ExecutorService worker = Executors.newCachedThreadPool(new NamedThreadFactory("NettyServerWorker", true));ChannelFactory channelFactory = new NioServerSocketChannelFactory(boss, worker, getUrl().getPositiveParameter(Constants.IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS));bootstrap = new ServerBootstrap(channelFactory);// ...... }再看ChannelFactory的構(gòu)造函數(shù):
public NioServerSocketChannelFactory(Executor bossExecutor, Executor workerExecutor, int workerCount) {this(bossExecutor, 1, workerExecutor, workerCount);}可以看出,boss線程池的大小為1,worker線程池的大小也是可以配置的,默認大小是當(dāng)前系統(tǒng)的核心數(shù)+1,也稱為IO線程。
busines(業(yè)務(wù)線程池)
為什么會有業(yè)務(wù)線程池,這里不多解釋,可以參考我上面的文章。
缺省是采用固定大小的線程池,dubbo提供了三種不同類型的線程池供用戶選擇。我們看看這個類:AllChannelHandler,它是其中一種handle,處理所有請求,它的一個作用就是調(diào)用業(yè)務(wù)線程池去執(zhí)行業(yè)務(wù)代碼,其中有獲取線程池的方法:
private ExecutorService getExecutorService() {ExecutorService cexecutor = executor;if (cexecutor == null || cexecutor.isShutdown()) { cexecutor = SHARED_EXECUTOR;}return cexecutor;}上面代碼中的變量executor來自于AllChannelHandler的父類WrappedChannelHandler,看下它的構(gòu)造函數(shù):
public WrappedChannelHandler(ChannelHandler handler, URL url) {//......executor = (ExecutorService) ExtensionLoader.getExtensionLoader(ThreadPool.class).getAdaptiveExtension().getExecutor(url);//...... }獲取線程池來自于SPI技術(shù),從代碼中可以看出線程池的缺省配置就是上面提到的固定大小線程池。
@SPI("fixed") public interface ThreadPool {/*** 線程池* * @param url 線程參數(shù)* @return 線程池*/@Adaptive({Constants.THREADPOOL_KEY})Executor getExecutor(URL url);}最后看下是如何將請求丟給線程池去執(zhí)行的,在AllChannelHandler中有這樣的方法:
public void received(Channel channel, Object message) throws RemotingException {ExecutorService cexecutor = getExecutorService();try {cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));} catch (Throwable t) {throw new ExecutionException(message, channel, getClass() + " error when process received event .", t);}}典型問題:拒絕服務(wù)
如果上面提到的dubbo線程池模型理解了,那么也就容易理解一個問題,當(dāng)前端大量請求并發(fā)出現(xiàn)時,很有可以將業(yè)務(wù)線程池中的線程消費完,因為默認缺省的線程池是固定大小(我現(xiàn)在版本缺省線程池大小為200),此時會出現(xiàn)服務(wù)無法按預(yù)期響應(yīng)的結(jié)果,當(dāng)然由于是固定大小的線程池,當(dāng)核心線程滿了后也有隊列可排,但默認是不排隊的,需要排隊需要單獨配置,我們可以從線程池的具體實現(xiàn)中看:
public class FixedThreadPool implements ThreadPool {public Executor getExecutor(URL url) {String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS, queues == 0 ? new SynchronousQueue<Runnable>() : (queues < 0 ? new LinkedBlockingQueue<Runnable>() : new LinkedBlockingQueue<Runnable>(queues)),new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));}}上面代碼的結(jié)論是:
- 默認線程池大小為200(不同的dubbo版本可能此值不同)
- 默認線程池不排隊,如果需要排隊,需要指定隊列的大小
當(dāng)業(yè)務(wù)線程用完后,服務(wù)端會報如下的錯誤:
Caused by: java.util.concurrent.RejectedExecutionException: Thread pool is EXHAUSTED! Thread Name: DubboServerHandler-192.168.10.222:9999, Pool Size: 1 (active: 1, core: 1, max: 1, largest: 1), Task: 8 (completed: 7), Executor status:(isShutdown:false, isTerminated:false, isTerminating:false), in dubbo://192.168.10.222:9999!at com.alibaba.dubbo.common.threadpool.support.AbortPolicyWithReport.rejectedExecution(AbortPolicyWithReport.java:53) ~[dubbo-2.5.3.jar:2.5.3]at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) [na:1.8.0_121]at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) [na:1.8.0_121]at com.alibaba.dubbo.remoting.transport.dispatcher.all.AllChannelHandler.caught(AllChannelHandler.java:65) ~[dubbo-2.5.3.jar:2.5.3]... 17 common frames omitted通過上面的分析,對調(diào)用的服務(wù)設(shè)置超時時間,是為了避免因為某種原因?qū)е戮€程被長時間占用,最終出現(xiàn)線程池用完返回拒絕服務(wù)的異常。
超時與服務(wù)降級
按我們文章之前的場景,web api 請求產(chǎn)品明細時調(diào)用product service,為了查詢產(chǎn)品評論product service調(diào)用comment service。如果此時由于comment service異常,響應(yīng)時間增大到10S(遠大于上游服務(wù)設(shè)置的超時時間),會發(fā)生超時異常,進而導(dǎo)致整個獲取產(chǎn)品明細的接口異常,這也就是平常說的強依賴。這類強依賴是超時不能解決的,解決方案一般是兩種:
- 調(diào)用comment service時做異常捕獲,返回空值或者返回具體的錯誤碼,消費端根據(jù)不同的錯誤碼做不同的處理。
- 調(diào)用coment service做服務(wù)降級,比如發(fā)生異常時返回一個mock的數(shù)據(jù),dubbo默認支持mock。
只有通過做異常捕獲或者服務(wù)降級才能確保某些不重要的依賴出問題時不影響主服務(wù)的穩(wěn)定性。而超時就可以與服務(wù)降級結(jié)合起來,當(dāng)消費端發(fā)生超時時自動觸發(fā)服務(wù)降級, 這樣即使我們的評論服務(wù)一直慢,但不影響獲取產(chǎn)品明細的主體功能,只不過會犧牲部分體驗,用戶看到的評論不是真實的,但評論相對是個邊緣功能,相比看不到產(chǎn)品信息要輕的多,某種程度上是可以舍棄的。
?
?
?
Dubbo超時機制導(dǎo)致的雪崩連接
BUG作者:?許曉
Bug?標題:?Dubbo超時機制導(dǎo)致的雪崩連接
Bug?影響:?Dubbo?服務(wù)提供者出現(xiàn)無法獲取?Dubbo?服務(wù)處理線程異常,后端?DB?爆出拿不到數(shù)據(jù)庫連接池,導(dǎo)致前端響應(yīng)時間異常飆高,系統(tǒng)處理能力下降,核心基礎(chǔ)服務(wù)無法提供正常服務(wù)。
Bug?發(fā)現(xiàn)過程:
線 上,對于高并發(fā)的服務(wù)化接口應(yīng)用,時常會出現(xiàn)Dubbo連接池爆滿情況,通常,我們理所應(yīng)當(dāng)?shù)恼J為,這是客戶端并發(fā)連接過高所致,一方面調(diào)整連接池大小, 一方面考慮去增加服務(wù)接口的機器,當(dāng)然也會考慮去優(yōu)化服務(wù)接口的應(yīng)用。很自然的,當(dāng)我們在線上壓測一個營銷頁面(為大促服務(wù),具備高并發(fā))時,我們遇到了 這種情況。而通過不斷的深入研究,我發(fā)現(xiàn)了一個特別的情況。
場景描述:
壓力從Jmeter壓至前端web應(yīng)用marketingfront,場景是批量獲取30個產(chǎn)品的信息。wsproductreadserver有一個批量接口,會循環(huán)從tair中獲取產(chǎn)品信息,若緩存不存在,則命中db。
壓測后有兩個現(xiàn)象:
1)?Dubbo的服務(wù)端爆出大量連接拿不到的異常,還伴隨著無法獲取數(shù)據(jù)庫連接池的情況
2)?Dubbo Consumer端有大量的Dubbo超時和重試的異常,且重試3次后,均失敗。
3)?Dubbo Consumer端的最大并發(fā)時91個
Dubbo Provider端的最大并發(fā)卻是600個,而服務(wù)端配置的dubbo最大線程數(shù)即為600。
這個時候,出于性能測試的警覺性,發(fā)現(xiàn)這兩個并發(fā)數(shù)極為不妥。
按照正常的請求模式,DubboConsumer和DubboProvider展示出來的并發(fā)應(yīng)該是一致的。此處為何會出現(xiàn)服務(wù)端的并發(fā)數(shù)被放大6倍,甚至有可能不止6倍,因為服務(wù)端的dubbo連接數(shù)限制就是600。
此處開始發(fā)揮性能測試各種大膽猜想:
1)是否是因為服務(wù)端再dubboServerHandle處理請求時,開啟了多線程,而這塊兒的多線程會累計到Dubbo的連接上,dragoon采集的這個數(shù)據(jù)可以真實的反應(yīng)目前應(yīng)用活動的線程對系統(tǒng)的壓力情況;
2)壓測環(huán)境不純潔?我的小伙伴們在偷偷和我一起壓測?(這個被我生生排除了,性能測試基本環(huán)境還是要保持獨立性)
3)是否是因為超時所致?這里超時會重試3次,那么順其自然的想,并發(fā)有可能最多會被放大到3倍,3*91=273<<600....還是不止3倍?
有了猜想,就得小心求證!
首先通過和dubbo開發(fā)人員 【草谷】分析,Dubbo連接數(shù)爆滿的原因,猜想1被否決,Dubbo服務(wù)端連接池是計數(shù)DubboServerHandle個數(shù)的業(yè)務(wù)是否采用多線程無關(guān)。
通過在壓測時,Dump provider端的線程數(shù),也證明了這個。
那么,可能還是和超時有很大關(guān)系。
再觀察wsproductreadserver接口的處理時間分布情況:
從?RT?的分布來看?。基本上?78.5%?的響應(yīng)時間是超過?1s?的。那么這個接口方法的?dubbo?超時時間是?500ms?,此時?dubbo?的重試機制會帶來怎樣的?雪崩效應(yīng)?呢?
如果按照上圖,雖然客戶端只有1個并發(fā)在做操作,但是由于服務(wù)端執(zhí)行十分耗時,每個請求的執(zhí)行RT遠遠超過了超時時間500ms,此時服務(wù)端的最大并發(fā)會有多少呢?
和服務(wù)端處理的響應(yīng)時間有特比特別大的關(guān)系。服務(wù)端處理時間變長,但是如果超時,客戶端的阻塞時間卻只有可憐的500ms,超過500ms,新一輪壓力又將發(fā)起。
上圖可直接看到的并發(fā)是8個,如果服務(wù)端RT再長些,那么并發(fā)可能還會再大些!
這也是為什么從marketingfront consumer的dragoon監(jiān)控來看,只有90個并發(fā)。但是到服務(wù)端,卻導(dǎo)致dubbo連接池爆掉的直接原因。
查看了wsproductreadserver的堆棧,600個dubboServerHandle大部分都在做數(shù)據(jù)庫的讀取和數(shù)據(jù)庫連接獲取以及tair的操作。
所以,為什么Dubbo服務(wù)端的連接池會爆掉?很有可能就是因為你的服務(wù)接口,在高并發(fā)下的大部分RT分布已經(jīng)超過了你的Dubbo設(shè)置的超時時間!這將直接導(dǎo)致Dubbo的重試機制會不斷放大你的服務(wù)端請求并發(fā)。
所 以如果,你在線上曾經(jīng)遇到過類似場景,您可以采取去除Dubbo的重試機器,并且合理的設(shè)置Dubbo的超時時間。目前國際站的服務(wù)中心,已經(jīng)開始去除 Dubbo的重試機制。當(dāng)然Dubbo的重試機制其實是非常好的QOS保證,它的路由機制,是會幫你把超時的請求路由到其他機器上,而不是本機嘗試,所以 dubbo的重試機器也能一定程度的保證服務(wù)的質(zhì)量。但是請一定要綜合線上的訪問情況,給出綜合的評估。
------------?等等等,別著急,我們似乎又忽略了一些細節(jié),元芳,你怎么看??------------------------
我們重新回顧剛才的業(yè)務(wù)流程架構(gòu),wsproductReadserver層有DB和tair兩級存儲。那么對于同樣接口為什么服務(wù)化的接口RT如此之差,按照前面提到的架構(gòu),包含tair緩存,怎么還會有數(shù)據(jù)庫連接獲取不到的情況?
接續(xù)深入追蹤,將問題暴露和開發(fā)討論,他們拿出tair
可以看到,客戶端提交批量查詢30個產(chǎn)品的產(chǎn)品信息。在服務(wù)端,有一個緩存模塊,緩存的key是產(chǎn)品的ID。當(dāng)產(chǎn)品命中tair時,則直接返回,若不命中,那么回去db中取數(shù),再放入緩存中。
這里可以發(fā)現(xiàn)一個潛在的性能問題:
客 戶端提交30個產(chǎn)品的查詢請求,而服務(wù)端,則通過for循環(huán)和tair交互,所以這個接口在通常情況下的性能估計也得超過60-100ms。如果不是30 個產(chǎn)品,而是50或者100,那么這個接口的性能將會衰減的非常厲害!(這純屬性能測試的yy,當(dāng)然這個暫時還不是我們本次關(guān)注的主要原因)
那么如此的架構(gòu),請求打在db上的可能性是比較小的, 由緩存命中率來保證。從線上真實的監(jiān)控數(shù)據(jù)來看,tair的命中率在70%,應(yīng)該說還不錯,為什么在我們的壓測場景,DB的壓力確是如此兇殘,甚至導(dǎo)致db的連接池?zé)o法獲取呢?
所以性能驗證場景就呼之欲出了:
場景:?準備30個產(chǎn)品ID,保持不變,這樣最多只會第一次會去訪問DB,并將數(shù)據(jù)存入緩存,后面將會直接命中緩存,db就在后面喝喝茶好了!
但是從測試結(jié)果來看,有兩點可以觀察到:
1)
2)
3)
于是開始檢查這30個產(chǎn)品到底有哪幾個沒有存入緩存。
通 過開發(fā)Debug預(yù)發(fā)布環(huán)境代碼,最終發(fā)現(xiàn),這兩個產(chǎn)品竟然已經(jīng)被用戶移到垃圾箱了。而通過和李浩和躍波溝通SellerCoponList的業(yè)務(wù)來 看,DA推送過來的產(chǎn)品是存在被用戶移除的可能性。因而,每次這兩個數(shù)據(jù)的查詢,由于數(shù)據(jù)庫查詢不到記錄,tair也沒有存儲相關(guān)記錄,導(dǎo)致這些查詢都將 經(jīng)過數(shù)據(jù)庫。數(shù)據(jù)庫壓力原因也找到了。
但是問題還沒有結(jié)束,這似乎只像是冰山表面,我們希望能夠鳥瞰整個冰山!
細細品味這個問題的最終性能表象?,?這是一種變向擊穿緩存的做法啊!也就是具備一定的通用性。如果接口始終傳入數(shù)據(jù)庫和緩存都不可能存在的數(shù)據(jù),那么每次的訪問都就落到db上,導(dǎo)致緩存變相擊穿,這個現(xiàn)象很有意思!
目前有一種解決方案,就是Null Object Pattern,將數(shù)據(jù)庫不存在的記錄也記錄到緩存中,但是value為NULL,使得緩存可以有效的攔截。由于數(shù)據(jù)的超時時間是10min,所以如果數(shù)據(jù)有所改動,也可以接受。
我相信這只是一種方案,可能還會有其他方案,但是這種變向的緩存擊穿卻讓我很興奮。回過頭來,如果讓我自己去實現(xiàn)這樣的緩存機制,數(shù)據(jù)庫和緩存都不存在的 數(shù)據(jù)場景很容易被忽略,并且這個對于業(yè)務(wù)確實也不會有影響。在線上存在大量熱點數(shù)據(jù)情況下,這樣的機制,往往并不會暴露性能問題。巧合的是,特定的場景, 性能卻會出現(xiàn)很大的偏差,這考驗的既是性能測試工程師的功力,也考驗的是架構(gòu)的功力!
Bug?解決辦法:
其實這過程中不僅僅有一些方法論,也有一些是性能測試經(jīng)驗的功底,更重要的是產(chǎn)出了一些通用性的性能問題解決方案,以及部分參數(shù)和技術(shù)方案的設(shè)計對系統(tǒng)架構(gòu)的影響。
1)對于核心的服務(wù)中心,去除dubbo超時重試機制,并重新評估設(shè)置超時時間。
2)對于存在tair或者其他中間件緩存產(chǎn)品,對NULL數(shù)據(jù)進行緩存,防止出現(xiàn)緩存的變相擊穿問題
?
?
?
Dubbo超時和重連機制
dubbo啟動時默認有重試機制和超時機制。
超時機制的規(guī)則是如果在一定的時間內(nèi),provider沒有返回,則認為本次調(diào)用失敗,
重試機制在出現(xiàn)調(diào)用失敗時,會再次調(diào)用。如果在配置的調(diào)用次數(shù)內(nèi)都失敗,則認為此次請求異常,拋出異常。
如果出現(xiàn)超時,通常是業(yè)務(wù)處理太慢,可在服務(wù)提供方執(zhí)行:jstack PID > jstack.log 分析線程都卡在哪個方法調(diào)用上,這里就是慢的原因。
如果不能調(diào)優(yōu)性能,請將timeout設(shè)大。
某些業(yè)務(wù)場景下,如果不注意配置超時和重試,可能會引起一些異常。
1.超時設(shè)置
DUBBO消費端設(shè)置超時時間需要根據(jù)業(yè)務(wù)實際情況來設(shè)定,
如果設(shè)置的時間太短,一些復(fù)雜業(yè)務(wù)需要很長時間完成,導(dǎo)致在設(shè)定的超時時間內(nèi)無法完成正常的業(yè)務(wù)處理。
這樣消費端達到超時時間,那么dubbo會進行重試機制,不合理的重試在一些特殊的業(yè)務(wù)場景下可能會引發(fā)很多問題,需要合理設(shè)置接口超時時間。
比如發(fā)送郵件,可能就會發(fā)出多份重復(fù)郵件,執(zhí)行注冊請求時,就會插入多條重復(fù)的注冊數(shù)據(jù)。
(1)合理配置超時和重連的思路
1.對于核心的服務(wù)中心,去除dubbo超時重試機制,并重新評估設(shè)置超時時間。
2.業(yè)務(wù)處理代碼必須放在服務(wù)端,客戶端只做參數(shù)驗證和服務(wù)調(diào)用,不涉及業(yè)務(wù)流程處理
(2)Dubbo超時和重連配置示例
| 1 2 | <!-- 服務(wù)調(diào)用超時設(shè)置為5秒,超時不重試-->? <dubbo:service?interface="com.provider.service.DemoService" ref="demoService"? retries="0" timeout="5000"/> |
2.重連機制
dubbo在調(diào)用服務(wù)不成功時,默認會重試2次。
Dubbo的路由機制,會把超時的請求路由到其他機器上,而不是本機嘗試,所以 dubbo的重試機制也能一定程度的保證服務(wù)的質(zhì)量。
但是如果不合理的配置重試次數(shù),當(dāng)失敗時會進行重試多次,這樣在某個時間點出現(xiàn)性能問題,調(diào)用方再連續(xù)重復(fù)調(diào)用,
系統(tǒng)請求變?yōu)檎V档膔etries倍,系統(tǒng)壓力會大增,容易引起服務(wù)雪崩,需要根據(jù)業(yè)務(wù)情況規(guī)劃好如何進行異常處理,何時進行重試。
?
?
?
?
?
淺談dubbo的ExceptionFilter異常處理
背景
我們的項目使用了dubbo進行不同系統(tǒng)之間的調(diào)用。 每個項目都有一個全局的異常處理,對于業(yè)務(wù)異常,我們會拋出自定義的業(yè)務(wù)異常(繼承RuntimeException)。 全局的異常處理會根據(jù)不同的異常類型進行不同的處理。 最近我們發(fā)現(xiàn),某個系統(tǒng)調(diào)用dubbo請求,provider端(服務(wù)提供方)拋出了自定義的業(yè)務(wù)異常,但consumer端(服務(wù)消費方)拿到的并不是自定義的業(yè)務(wù)異常。 這是為什么呢?還需要從dubbo的ExceptionFilter說起。ExceptionFilter
如果Dubbo的?provider端?拋出異常(Throwable),則會被?provider端?的ExceptionFilter攔截到,執(zhí)行以下invoke方法: [java]?view plaincopy代碼分析
按邏輯順序進行分析,滿足其中一個即返回,不再繼續(xù)執(zhí)行判斷。邏輯0
[java]?view plaincopy不適用于此場景,不在此處探討。
邏輯1
[java]?view plaincopyprovider端想拋出受檢異常,必須在api上明確寫明拋出受檢異常;consumer端如果要處理受檢異常,也必須使用明確寫明拋出受檢異常的api。
provider端api新增 自定義的 受檢異常, 所有的 consumer端api都必須升級,同時修改代碼,否則無法處理這個特定異常。
consumer端DecodeableRpcResult的decode方法會對異常進行處理
此處會拋出IOException,上層catch后會做toString處理,放到mErrorMsg屬性中: [java]?view plaincopy
DefaultFuture判斷請求返回的結(jié)果,最后拋出RemotingException: [java]?view plaincopy
DubboInvoker捕獲RemotingException,拋出RpcException: [java]?view plaincopy
調(diào)用棧:
FailOverClusterInvoker.doInvoke -...-> DubboInvoker.doInvoke -> ReferenceCountExchangeClient.request -> HeaderExchangeClient.request -> HeaderExchangeChannel.request -> AbstractPeer.send -> NettyChannel.send -> AbstractChannel.write -> Channels.write --back_to--> DubboInvoker.doInvoke -> DefaultFuture.get -> DefaultFuture.returnFromResponse -> throw new RemotingException 異常示例: [java]?view plaincopy
?
邏輯2
[java]?view plaincopy答案是和上面一樣,拋出RpcException。
因此如果consumer端不care這種異常,則不需要任何處理;
consumer端有這種異常(路徑要完全一致,包名+類名),則不需要任何處理;
沒有這種異常,又想進行處理,則需要引入這個異常進行處理(方法有多種,比如升級api,或引入/升級異常所在的包)。
邏輯3
[java]?view plaincopy邏輯4
[java]?view plaincopy邏輯5
[java]?view plaincopy邏輯6
[java]?view plaincopy核心思想
盡力避免反序列化時失敗(只有在jdk版本或api版本不一致時才可能發(fā)生)。如何正確捕獲業(yè)務(wù)異常
了解了ExceptionFilter,解決上面提到的問題就很簡單了。 有多種方法可以解決這個問題,每種都有優(yōu)缺點,這里不做詳細分析,僅列出供參考: 1.?將該異常的包名以"java.或者"javax. " 開頭 2.?使用受檢異常(繼承Exception) 3.?不用異常,使用錯誤碼 4.?把異常放到provider-api的jar包中 5.?判斷異常message是否以XxxException.class.getName()開頭(其中XxxException是自定義的業(yè)務(wù)異常) 6.?provider實現(xiàn)GenericService接口 7.?provider的api明確寫明throws XxxException,發(fā)布provider(其中XxxException是自定義的業(yè)務(wù)異常) 8. 實現(xiàn)dubbo的filter,自定義provider的異常處理邏輯(方法可參考之前的文章給dubbo接口添加白名單——dubbo Filter的使用)?給dubbo接口添加白名單——dubbo Filter的使用具體內(nèi)容如下:
在開發(fā)中,有時候需要限制訪問的權(quán)限,白名單就是一種方法。對于Java Web應(yīng)用,Spring的攔截器可以攔截Web接口的調(diào)用;而對于dubbo接口,Spring的攔截器就不管用了。
dubbo提供了Filter擴展,可以通過自定義Filter來實現(xiàn)這個功能。本文通過一個事例來演示如何實現(xiàn)dubbo接口的IP白名單。
?
實現(xiàn)com.alibaba.dubbo.rpc.Filter接口:
[java]?view plaincopy
dubbo自己會對這些bean進行注入,不需要再標注@Resource讓Spring注入,參見擴展點加載
參考:調(diào)用攔截擴展
在resources目錄下添加純文本文件META-INF/dubbo/com.alibaba.dubbo.rpc.Filter,內(nèi)容如下:
[plain]?view plaincopy
[html]?view plaincopy
這樣就可以實現(xiàn)dubbo接口的IP白名單功能了。
?
轉(zhuǎn)載于:https://www.cnblogs.com/xuwc/p/8974709.html
總結(jié)
以上是生活随笔為你收集整理的dubbo超时重试和异常处理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 读写注册表
- 下一篇: 各大网站CSS代码初始化集合