日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

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

前 言

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

又踩到Dubbo的坑,但是這次我笑不出來(lái)

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

什么是ConcurrentModificationException?

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

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

又踩到Dubbo的坑,但是這次我笑不出來(lái)

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

難點(diǎn)分析

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

但是肥朝,你說(shuō)了這么多,我還是沒(méi)發(fā)現(xiàn)這個(gè)問(wèn)題有什么難的??!

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

會(huì)不會(huì)有同學(xué)在線程開(kāi)啟前拿到RpcContext,然后在新線程中,做set操作(圖中的get操作是沒(méi)有問(wèn)題的)。

又踩到Dubbo的坑,但是這次我笑不出來(lái)

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

索然無(wú)味還是柳暗花明?

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

我們先來(lái)看一下官方文檔對(duì)RpcContext的介紹

又踩到Dubbo的坑,但是這次我笑不出來(lái)

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

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

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

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

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

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

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

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

源碼細(xì)節(jié)

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

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

又踩到Dubbo的坑,但是這次我笑不出來(lái)

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

又踩到Dubbo的坑,但是這次我笑不出來(lái)

你不看代碼直接聽(tīng)我說(shuō)也行,這幾段代碼的意思是,在一個(gè)提供者的方法中,canRemove會(huì)設(shè)置為false的,所以,他們?cè)谶@個(gè)方法體遠(yuǎn)程調(diào)用中,是沒(méi)辦法清空RpcContext的,需要在整體調(diào)用完才會(huì)清空。

我們?cè)倩仡櫼幌掳赴l(fā)現(xiàn)場(chǎng)

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

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

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

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

那么,究竟是何人所為!我們隨著鏡頭,再次深入源碼!既然是RpcContext給人搞了,那么我們就從這里順藤摸瓜,這里先省略肥朝的內(nèi)心戲,我們來(lái)看重點(diǎn)。在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);
????????????}
????????}
????});
}

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

聽(tīng)完上面的分析,你是不是明白了點(diǎn)啥?新線程,還能拿到舊的RpcContext。那么,有這么一個(gè)場(chǎng)景,我們?cè)谕ㄟ^(guò)提供者方法中,發(fā)起兩個(gè)異步請(qǐng)求,第一個(gè)請(qǐng)求走FilteronResponse(響應(yīng)結(jié)果)的時(shí)候,我們?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操作,第二個(gè)請(qǐng)求又正好發(fā)起,而發(fā)起又會(huì)經(jīng)歷putAll這步驟,就會(huì)出現(xiàn)這個(gè)并發(fā)修改異常。于是乎,真相大白!

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

拓展性思考

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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

我們要知道怎么正確寫(xiě)代碼,那直接去抄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和他的作用,就很容易知道,有類(lèi)似需求,代碼應(yīng)該怎么寫(xiě)了。我光告訴你怎么寫(xiě)代碼沒(méi)用啊,我要告訴你,遇到問(wèn)題,怎么去抄正確代碼,讓你任何時(shí)候,都有得cao!


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

又踩到Dubbo的坑,但是這次我笑不出來(lái)

又踩到Dubbo的坑,但是這次我笑不出來(lái)

又踩到Dubbo的坑,但是這次我笑不出來(lái)

長(zhǎng)按訂閱更多精彩▼

又踩到Dubbo的坑,但是這次我笑不出來(lái)

如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

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

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

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

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

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

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

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

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

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

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

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

關(guān)鍵字: 電動(dòng)汽車(chē) 新能源 驅(qū)動(dòng)電源

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

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

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

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

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

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

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

關(guān)鍵字: LED 驅(qū)動(dòng)電源 開(kāi)關(guān)電源

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

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