記一次sleuth發(fā)送zipkin異常引起的OOM

一示损、問(wèn)題背景

一次生產(chǎn)事故寿弱,線上服務(wù)響應(yīng)慢犯眠;
作為常規(guī)操作,服務(wù)的VM啟動(dòng)參數(shù)有配置OOM提取內(nèi)存DUMP信息:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/dump-path/

這是個(gè)好習(xí)慣症革。

使用Eclipse MAT分析dump文件筐咧,大對(duì)象視圖如下:

大對(duì)象

一種對(duì)象占據(jù)了1.8G的JVM內(nèi)存空間,程序配置的最大堆大小是2G噪矛;很明顯量蕊,這是由于程序問(wèn)題引起的單一對(duì)象大量產(chǎn)生,而又一直引用可達(dá)艇挨,造成JVM無(wú)法GC引起的OOM残炮。

二、MAT分析

接下來(lái)繼續(xù)使用MAT缩滨,分析對(duì)象產(chǎn)生的堆棧:

總結(jié)性描述

這是一個(gè)總結(jié)性的描述势就,意思是一個(gè)zipkin2.reporter.InMemoryReporterMetrics類的實(shí)例占據(jù)了96.09%的堆空間,而內(nèi)存的增加是由于java.util.concurrent.ConcurrentHashMap$Node[]實(shí)例的堆積引起的脉漏。

通過(guò)這個(gè)總結(jié)性的描述信息苞冯,大概能夠知道去InMemoryReporterMetrics這個(gè)類找問(wèn)題了。

1)到內(nèi)存積累點(diǎn)的最短路徑

MAT還提供了視圖Shortest Paths to the Accumulation Point來(lái)定位大對(duì)象產(chǎn)生的引用關(guān)系:

最短路徑

通過(guò)這個(gè)視圖侧巨,大對(duì)象的引用關(guān)系是:

AsyncReporter.Builder ->
AsyncReporter.BoundedAsyncReporter(metrics屬性) ->
InMemoryReporterMetrics(messagesDropped屬性)

2)大對(duì)象內(nèi)容

既然大對(duì)象是ConcurrentHashMap$Node的實(shí)例舅锄,那么可以通過(guò)了解Node的具體內(nèi)容,來(lái)定位問(wèn)題司忱;

通過(guò)MAT巧娱,還可以看到堆積的大對(duì)象的具體內(nèi)容碉怔。

操作方式是:

outgoing references

得到大對(duì)象內(nèi)容:

大對(duì)象內(nèi)容

任意選取一個(gè)對(duì)象烘贴,通過(guò)查看Map的Node內(nèi)容禁添,發(fā)現(xiàn):

  1. key是一個(gè)異常類,具體是ResourceAccessException
  2. value是一個(gè)自動(dòng)Long AtomicLong
  3. key這個(gè)異常的產(chǎn)生原因是:對(duì)http://localhost:9411/api/v2/spans這個(gè)地址的POST被拒絕

三桨踪、源碼分析

使用MAT工具分析DUMP老翘,已經(jīng)得出了很多信息,甚至已經(jīng)知道問(wèn)題原因锻离。但是還需要進(jìn)一步分析源碼铺峭,詳細(xì)了解問(wèn)題的產(chǎn)生,以及解決方法汽纠。

1)InMemoryReporterMetrics

通過(guò)MAT分析得出的大對(duì)象引用關(guān)系卫键,查看類InMemoryReporterMetrics:

private final ConcurrentHashMap<Throwable, AtomicLong> messagesDropped =
      new ConcurrentHashMap<Throwable, AtomicLong>();

messagesDropped是一個(gè)key 為Throwable,value為AtomicLongConcurrentHashMap虱朵。

InMemoryReporterMetrics莉炉,看名字,它是一個(gè)內(nèi)存報(bào)告度量碴犬。具體對(duì)是sleuth發(fā)送到zipkin服務(wù)器的所有消息的一個(gè)統(tǒng)計(jì)絮宁,包括發(fā)送成功的消息,發(fā)送失敗的消息服协。注意這個(gè)統(tǒng)計(jì)信息是存在內(nèi)存里的绍昂。
而這個(gè)度量中的messagesDropped就是存儲(chǔ)發(fā)送異常的消息,key是具體異常信息偿荷,value是出現(xiàn)次數(shù)窘游。

那么推斷如果發(fā)送zipkin異常不斷產(chǎn)生,那么messagesDropped的不斷堆積跳纳,勢(shì)必會(huì)造成OOM忍饰。

2)AsyncReporter

從引用關(guān)系上來(lái)看,InMemoryReporterMetrics是由AsyncReporter.BoundedAsyncReporter中的屬性metrics引用的:

static final class BoundedAsyncReporter<S> extends AsyncReporter<S> {

final ReporterMetrics metrics;
}

在這個(gè)類的flush()方法中棒旗,有這樣一段代碼:

void flush(BufferNextMessage<S> bundler) {
     try {
        sender.sendSpans(nextMessage).execute();
      } catch (IOException | RuntimeException | Error t) {
            // In failure case, we increment messages and spans dropped.
            metrics.incrementMessagesDropped(t);
      }
}

可以看到喘批,當(dāng)sender發(fā)送消息到zipkin產(chǎn)生異常時(shí),就會(huì)將異常實(shí)例本身铣揉,存入metricsmessagesDropped中饶深。

AsyncReporter類使用了build模式,來(lái)創(chuàng)建異步報(bào)告者(AsyncReporter)逛拱,而這個(gè)異步報(bào)告者的具體類敌厘,就是AsyncReporter的內(nèi)部類BoundedAsyncReporter

AsyncReporter.Builderbuilder()方法中朽合,啟動(dòng)了一個(gè)線程俱两,在一個(gè)while循環(huán)中饱狂,不斷將消息隊(duì)列中的消息flush到zipkin。這就是異步reporter的由來(lái)宪彩。

3)zipkin自動(dòng)配置

SpringBoot的自動(dòng)配置休讳,其實(shí)就是根據(jù)相關(guān)必須條件,將具備各種功能的bean注入到spring上下文中尿孔。zipkin的自動(dòng)配置也不例外:

自動(dòng)配置類ZipkinAutoConfiguration創(chuàng)建異步報(bào)告者的方法如下:

@Bean
@ConditionalOnMissingBean
public Reporter<Span> reporter(
        ReporterMetrics reporterMetrics,
        ZipkinProperties zipkin,
        Sender sender,
        BytesEncoder<Span> spanBytesEncoder
) {
    return AsyncReporter.builder(sender)
            .queuedMaxSpans(1000) // historical constraint. Note: AsyncReporter supports memory bounds
            .messageTimeout(zipkin.getMessageTimeout(), TimeUnit.SECONDS)
            .metrics(reporterMetrics)
            .build(spanBytesEncoder);
}

這個(gè)類中俊柔,還創(chuàng)建了發(fā)送到zipkin所需的sender,以及我們的關(guān)注點(diǎn)ReporterMetrics

@Bean
@ConditionalOnMissingBean
ReporterMetrics sleuthReporterMetrics() {
    return new InMemoryReporterMetrics(); 
}

四活合、問(wèn)題原因

服務(wù)在開發(fā)測(cè)試時(shí)雏婶,使用了zipkin的調(diào)用鏈追蹤。但是投產(chǎn)時(shí)白指,由于某些原因留晚,無(wú)法使用zipkin,于是將zipkin的相關(guān)配置注釋掉了告嘲。

因此服務(wù)有zipkin的依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

但是沒(méi)有zipkin的配置:

# 調(diào)用鏈
#  zipkin:
#    base-url: http://172.20.6.23:9412
#  sleuth:
#    sampler:
#      probability: 1.0 # 采樣率, 默認(rèn)為0.1, 采樣10%的請(qǐng)求

通過(guò)觀察zipkin的自動(dòng)配置類ZipkinAutoConfiguration

@EnableConfigurationProperties({ZipkinProperties.class, SamplerProperties.class})
@ConditionalOnProperty(value = "spring.zipkin.enabled", matchIfMissing = true)
public class ZipkinAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Reporter<Span> reporter(
            ReporterMetrics reporterMetrics,
            ZipkinProperties zipkin,
            Sender sender,
            BytesEncoder<Span> spanBytesEncoder
    ) {
        return AsyncReporter.builder(sender)
                .queuedMaxSpans(1000) // historical constraint. Note: AsyncReporter supports memory bounds
                .messageTimeout(zipkin.getMessageTimeout(), TimeUnit.SECONDS)
                .metrics(reporterMetrics)
                .build(spanBytesEncoder);
    }
}

即使沒(méi)有任何zipkin的配置错维,都會(huì)創(chuàng)建一個(gè)異步報(bào)告者,默認(rèn)的采樣率是:

private float probability = 0.1f;

所以即使不配置相關(guān)配置項(xiàng)状蜗,也會(huì)以默認(rèn)采樣率10%需五,發(fā)送到zipkin,這是默認(rèn)的地址是:

@ConfigurationProperties("spring.zipkin")
public class ZipkinProperties {
    /**
     *  URL of the zipkin query server instance. You can also provide
     *  the service id of the Zipkin server if Zipkin's registered in
     *  service discovery (e.g. http://zipkinserver/)
     */
    private String baseUrl = "http://localhost:9411/";
}

此時(shí)發(fā)送到localhost顯然會(huì)連接拒絕轧坎。導(dǎo)致度量中的異常實(shí)例堆積宏邮,從而OOM。

五缸血、問(wèn)題解決

通過(guò)MAT分析和源碼分析蜜氨,可以容易得到問(wèn)題原因是zipkin地址的問(wèn)題,那么把地址配置正確應(yīng)該就可以解決問(wèn)題捎泻。

更深層次的問(wèn)題

通過(guò)分析得出飒炎,其實(shí)隨異步發(fā)送者創(chuàng)建的InMemoryReporterMetrics是有缺陷的隔箍;
因?yàn)槿粲捎谝恍┎豢深A(yù)知的原因?qū)е掳l(fā)送zipkin產(chǎn)生異常惠险,那么這個(gè)異常信息會(huì)存放到內(nèi)存度量中(InMemoryReporterMetrics)五慈,而且又沒(méi)有機(jī)制去刪除导而。若不斷堆積,還是會(huì)產(chǎn)生OOM洽沟。

這一點(diǎn)扼睬,不知道是不是zipkin的設(shè)計(jì)缺陷床嫌。

解決辦法

同事提出可以創(chuàng)建一個(gè)空的度量哄孤,來(lái)替換原來(lái)的內(nèi)存度量:

@Bean
public ReporterMetrics metrics() {
    return new ReporterMetrics() {

        @Override
        public void incrementMessages() {
            
        }

        @Override
        public void incrementMessagesDropped(Throwable cause) {

        }

        @Override
        public void incrementSpans(int quantity) {

        }

        @Override
        public void incrementSpanBytes(int quantity) {

        }

        @Override
        public void incrementMessageBytes(int quantity) {

        }

        @Override
        public void incrementSpansDropped(int quantity) {

        }

        @Override
        public void updateQueuedSpans(int update) {

        }

        @Override
        public void updateQueuedBytes(int update) {

        }
    };
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末照筑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凝危,老刑警劉巖波俄,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蛾默,居然都是意外死亡懦铺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門趴生,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)阀趴,“玉大人,你說(shuō)我怎么就攤上這事苍匆。” “怎么了棚菊?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵浸踩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我统求,道長(zhǎng)检碗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任码邻,我火速辦了婚禮折剃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘像屋。我一直安慰自己怕犁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布己莺。 她就那樣靜靜地躺著奏甫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪凌受。 梳的紋絲不亂的頭發(fā)上阵子,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音胜蛉,去河邊找鬼挠进。 笑死,一個(gè)胖子當(dāng)著我的面吹牛誊册,可吹牛的內(nèi)容都是我干的领突。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼解虱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼攘须!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤于宙,失蹤者是張志新(化名)和其女友劉穎浮驳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捞魁,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡至会,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谱俭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奉件。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖昆著,靈堂內(nèi)的尸體忽然破棺而出县貌,到底是詐尸還是另有隱情,我是刑警寧澤凑懂,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布煤痕,位于F島的核電站,受9級(jí)特大地震影響接谨,放射性物質(zhì)發(fā)生泄漏摆碉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一脓豪、第九天 我趴在偏房一處隱蔽的房頂上張望巷帝。 院中可真熱鬧,春花似錦扫夜、人聲如沸楞泼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)现拒。三九已至,卻和暖如春望侈,著一層夾襖步出監(jiān)牢的瞬間印蔬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工脱衙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留侥猬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓捐韩,卻偏偏與公主長(zhǎng)得像退唠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荤胁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355