www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > > 架構(gòu)師社區(qū)
[導(dǎo)讀]由于線上具體異常包含信息量過大,秉承讓肥朝的粉絲沒有難調(diào)試的代碼的原則,我特意抽取了一個復(fù)現(xiàn)的demo放在了git,讓你不在現(xiàn)場,一樣享受到排查的快樂!但是最近,太多假粉伸手黨拿到地址就跑,因此我把地址藏在本文某個角落,因此認真看文的才能找到!

前 言

直入主題,線上應(yīng)用發(fā)現(xiàn),偶發(fā)性出現(xiàn)如下異常日志

又踩到Dubbo的坑,但是這次我笑不出來

又踩到Dubbo的坑,但是這次我笑不出來

當(dāng)然由于線上具體異常包含信息量過大,秉承讓肥朝的粉絲沒有難調(diào)試的代碼的原則,我特意抽取了一個復(fù)現(xiàn)的demo放在了git,讓你不在現(xiàn)場,一樣享受到排查的快樂!但是最近,太多假粉伸手黨拿到地址就跑,因此我把地址藏在本文某個角落,因此認真看文的才能找到!(重點)

又踩到Dubbo的坑,但是這次我笑不出來

由于工作性質(zhì)的原因,上班時間根本抽不出時間做其他事,修bug,都只能下班時間來做,因此周六就到公司搬磚了。

又踩到Dubbo的坑,但是這次我笑不出來

什么是ConcurrentModificationException?

中文意思就是,并發(fā)修改異常。也就是我們常說的fail-fast(快速失敗)。當(dāng)然肥朝更認為,快速失敗是一種思想,比如Spring會在啟動的時候做大量的檢查,什么bean找不到,依賴注入錯誤等等,都會把一些顯而易見的錯誤檢查出來,防止在項目跑著跑著期間再失敗,也就是提前檢查。無論是業(yè)務(wù)開發(fā),還是基礎(chǔ)組件開發(fā),亦或是生活中,這個思想都是可以用到的。

那么,言歸正傳,這個異常到底什么意思啊。簡單說就是,當(dāng)一個集合在遍歷的時候,他的元素也正在被修改。剛學(xué)java那會,我們邊遍歷邊刪除就會出現(xiàn)這個異常。ConcurrentModificationException的原理這些網(wǎng)上太多,肥朝就暫且不提。那么我們來看下異常棧。

又踩到Dubbo的坑,但是這次我笑不出來

又踩到Dubbo的坑,但是這次我笑不出來

好了,我們已經(jīng)找到了RpcContext.getContext().getObjectAttachments()正在遍歷。那么,只要找到誰在修改他就行了啊,就這?

又踩到Dubbo的坑,但是這次我笑不出來

難點分析

很明顯,這里面并不存在遍歷的同時修改元素,Dubbo的代碼還不至于有這個明顯的bug。出現(xiàn)ConcurrentModificationException,就有可能是,A線程在遍歷,B線程在修改。

但是肥朝,你說了這么多,我還是沒發(fā)現(xiàn)這個問題有什么難的?。?/p>

這個問題難點主要在于,在Dubbo里面,RpcContext是對應(yīng)一個線程的,你可以簡單理解為ThreadLocal的增強版。也就是說,A線程拿出來的,和B線程拿出來的RpcContext都不是同一個,何來并發(fā)修改同一個之說?當(dāng)然官方文檔給了我一個啟示

又踩到Dubbo的坑,但是這次我笑不出來

會不會有同學(xué)在線程開啟前拿到RpcContext,然后在新線程中,做set操作(圖中的get操作是沒有問題的)。

又踩到Dubbo的坑,但是這次我笑不出來

于是,似乎豁然開朗的我,順著這條線索,周六加了一天班,把代碼翻了個遍,最后發(fā)現(xiàn)沒有找到。

又踩到Dubbo的坑,但是這次我笑不出來

索然無味還是柳暗花明?

并發(fā)這東西,要么不出問題,一旦出問題都是很難找。觀察了線上日志,重現(xiàn)概率很小,就一小段日志,并且業(yè)務(wù)方很忙,也沒時間配合你查問題。于是只能順著源碼,把Dubbo的整個請求到響應(yīng)的過程在腦海中快速過幾遍,看看哪個環(huán)節(jié)有可能出問題,做了無數(shù)的假設(shè)。隨著一次次的假設(shè)失敗,在即將身體索然無味之際,還真發(fā)現(xiàn)了一些蛛絲馬跡?。ㄗ⒁?,本文所用到的,都是dubbo2.7.6)

我們先來看一下官方文檔對RpcContext的介紹

又踩到Dubbo的坑,但是這次我笑不出來

好了,那么我問你,下面這段代碼,love能輸出什么?

@Service
public?class?AHelloServiceImpl?implements?AHelloService?{

????@Reference
????private?BHelloService?bHelloService;

????@Override
????public?String?sayHello()?throws?Exception{

????????RpcContext.getContext().setAttachment("我最愛的人是?","肥朝");
????????bHelloService.sayHello();
????????String?love?=?RpcContext.getContext().getAttachment("我最愛的人是?");
????????System.out.println("this?is:?"?+?love);
????????Thread.sleep(10L);

????????bHelloService.sayHello();

????????return?"歡迎關(guān)注微信公眾號:肥朝";
????}
}

我在圖都圈得這么明顯了,看得懂中文都知道,發(fā)起一次遠程調(diào)用后,參數(shù)會被清空,下面肯定get不到的啦。但是其實是get得到的,不要問肥朝為什么都知道圖是有問題的,還特意圈起來騙你,我只想讓你知道社會險惡。

源碼細節(jié)

閱讀過源碼,和對源碼有細節(jié)深入思考,效果是很大不一樣的。

我們來看一下源碼就知道了。文中說的會清除,對應(yīng)的代碼是怎么樣的呢?

又踩到Dubbo的坑,但是這次我笑不出來

又踩到Dubbo的坑,但是這次我笑不出來

如果作為正常的客戶端調(diào)用,那么,在調(diào)用后確實是會刪除的。但是如果你對源碼細節(jié)足夠熟悉你就會發(fā)現(xiàn),在org.apache.dubbo.rpc.filter.ContextFilter這個類中

又踩到Dubbo的坑,但是這次我笑不出來

又踩到Dubbo的坑,但是這次我笑不出來

你不看代碼直接聽我說也行,這幾段代碼的意思是,在一個提供者的方法中,canRemove會設(shè)置為false的,所以,他們在這個方法體遠程調(diào)用中,是沒辦法清空RpcContext的,需要在整體調(diào)用完才會清空。

我們再回顧一下案發(fā)現(xiàn)場

@Override
public?String?sayHello()?throws?Exception{

????bHelloService.sayHello();
????Thread.sleep(10L);
????bHelloService.sayHello();

????return?"歡迎關(guān)注微信公眾號:肥朝";
}

從目前得到的信息很明顯知道,第一次遠程調(diào)用,和第二次遠程調(diào)用,用的是同一個RpcContext,并且,在第二次遠程調(diào)用的時候。這個RpcContext的內(nèi)容,給人動了手腳了。

那么,究竟是何人所為!我們隨著鏡頭,再次深入源碼!既然是RpcContext給人搞了,那么我們就從這里順藤摸瓜,這里先省略肥朝的內(nèi)心戲,我們來看重點。在RpcContext中發(fā)現(xiàn)一段可疑片段

public?static?void?restoreContext(RpcContext?oldContext)?{
????LOCAL.set(oldContext);
}

接著繼續(xù)順豐摸瓜,發(fā)現(xiàn)調(diào)用這段代碼的邏輯是

/**
?*?tmp?context?to?use?when?the?thread?switch?to?Dubbo?thread.
?*/

private?RpcContext?tmpContext;

private?RpcContext?tmpServerContext;
private?BiConsumer?beforeContext?=?(appResponse,?t)?->?{
????tmpContext?=?RpcContext.getContext();
????tmpServerContext?=?RpcContext.getServerContext();
????RpcContext.restoreContext(storedContext);
????RpcContext.restoreServerContext(storedServerContext);
};

private?BiConsumer?afterContext?=?(appResponse,?t)?->?{
????RpcContext.restoreContext(tmpContext);
????RpcContext.restoreServerContext(tmpServerContext);
};
public?Result?whenCompleteWithContext(BiConsumer?fn)?{
????this.responseFuture?=?this.responseFuture.whenComplete((v,?t)?->?{
????????beforeContext.accept(v,?t);
????????fn.accept(v,?t);
????????afterContext.accept(v,?t);
????});
????return?this;
}
@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);
????????????}
????????}
????});
}

看不懂代碼不要怕,肥朝大白話解釋一下。你就想象一個Dubbo異步場景,Dubbo異步回調(diào)結(jié)果的時候,是會開啟一個新的線程,那么,這個回調(diào)就和當(dāng)初請求不在一個線程里面了,因此這個回調(diào)線程是拿不到當(dāng)初請求的RpcContext。但是我們清空RpcContext是需要在一次請求結(jié)束的時候,也就是說,雖然異步回調(diào)是另外一個線程了,但是我們?nèi)匀恍枰玫疆?dāng)初請求時候的RpcContext來走Filter,做清空等操作。上面那段代碼就是做,切換線程怎么拿回之前的RpcContext

聽完上面的分析,你是不是明白了點啥?新線程,還能拿到舊的RpcContext。那么,有這么一個場景,我們在通過提供者方法中,發(fā)起兩個異步請求,第一個請求走FilteronResponse(響應(yīng)結(jié)果)的時候,我們?nèi)绻?code style="margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">Filter做RpcContext.getContext().setAttachment操作,第二個請求又正好發(fā)起,而發(fā)起又會經(jīng)歷putAll這步驟,就會出現(xiàn)這個并發(fā)修改異常。于是乎,真相大白!

具體詳情,親自調(diào)試一番就會清楚,肥朝公眾號回復(fù)modification獲取git地址

拓展性思考

真相大白就結(jié)束了?熟悉肥朝的粉絲都知道,我們遇到問題,要盡量壓榨問題的全部價值!比如,你說不要在攔截器中onResponse方法中用RpcContext.getContext().setAttachment這樣的操作,但是我們確實有類似需要,那到底要怎么寫代碼又不說,你這樣叫我怎么給你轉(zhuǎn)發(fā)文章!

又踩到Dubbo的坑,但是這次我笑不出來

我們要知道怎么正確寫代碼,那直接去抄Dubbo其他攔截器的代碼不就知道了?比如

@Activate(group?=?PROVIDER,?order?=?-10000)
public?class?ContextFilter?implements?Filter,?Filter.Listener?{


????@Override
????public?void?onResponse(Result?appResponse,?Invoker?invoker,?Invocation?invocation)?{
????????//?pass?attachments?to?result
????????appResponse.addObjectAttachments(RpcContext.getServerContext().getObjectAttachments());
????}

}

我們很明顯看到,你熟悉一下appResponse的api和他的作用,就很容易知道,有類似需求,代碼應(yīng)該怎么寫了。我光告訴你怎么寫代碼沒用啊,我要告訴你,遇到問題,怎么去抄正確代碼,讓你任何時候,都有得cao!


特別推薦一個分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

又踩到Dubbo的坑,但是這次我笑不出來

又踩到Dubbo的坑,但是這次我笑不出來

又踩到Dubbo的坑,但是這次我笑不出來

長按訂閱更多精彩▼

又踩到Dubbo的坑,但是這次我笑不出來

如有收獲,點個在看,誠摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當(dāng)下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應(yīng)用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉