記一次 HttpClient 連接半釋放導(dǎo)致的問(wèn)題

問(wèn)題描述

啟動(dòng) 0.9.2 版本的 hugegraph-server 和 hugegraph-studio扭粱,隨便執(zhí)行兩條查詢語(yǔ)句悟狱,然后停止 hugegraph-server滔蝉,再啟動(dòng)壳炎,提示 “8080 端口被占用”搁痛。

詳細(xì)

問(wèn)題定位

Step 1

最開始沒(méi)仔細(xì)想长搀,主要也是因?yàn)閷?duì) TCP 這塊沒(méi)啥實(shí)際經(jīng)驗(yàn),乍一看以為是 studio 每次執(zhí)行 gremlin 建立一個(gè)連接且連接未關(guān)閉導(dǎo)致的問(wèn)題鸡典。于是簡(jiǎn)單的在 studio
每個(gè)請(qǐng)求處理完成后將 HugeClient 關(guān)閉掉源请,即 finally{ client.close() },然后測(cè)試彻况,每個(gè)請(qǐng)求處理完谁尸,連接都正常關(guān)閉,然后停止 hugegraph-server
再重啟纽甘,果然就沒(méi)問(wèn)題了良蛮,看來(lái)這個(gè)問(wèn)題很簡(jiǎn)單嘛。

但是這么搞總覺得太暴力了悍赢,按照書上和博客的說(shuō)法决瞳,連接的建立/釋放都是不小的開銷货徙,咱能不能重用他呢?

Step 2

在 studio 中重用 HugeClient 也很簡(jiǎn)單皮胡,簡(jiǎn)單點(diǎn)的話痴颊,把它定義為靜態(tài)的,然后請(qǐng)求入口處以單例的形式獲取 HugeClient 即可屡贺。我在 loader 中已經(jīng)寫過(guò)一個(gè)
HugeClientWarpper蠢棱,所以這里也很快完成。再次測(cè)試甩栈,除第一次請(qǐng)求創(chuàng)建了一個(gè)連接外泻仙,后面的請(qǐng)求都沒(méi)有創(chuàng)建新的連接,看起來(lái)節(jié)約了開銷量没。然后停止
hugegraph-server 再重啟玉转,結(jié)果又提示了 “8080 端口被占用”。再查看 studio 進(jìn)程的 TCP 連接使用情況允蜈,發(fā)現(xiàn)還是有一個(gè)處于 CLOSE_WAIT 狀態(tài)的連接冤吨,
并且這個(gè)連接一直不會(huì)關(guān)閉。

除了停止 hugegraph-server 會(huì)產(chǎn)生 CLOSE_WAIT 的連接外饶套,讓 hugegraph-server 閑置一會(huì)也會(huì)在 studio 進(jìn)程中產(chǎn)生 CLOSE_WAIT 狀態(tài)的連接漩蟆,但是只要
studio 再請(qǐng)求一次,那個(gè) CLOSE_WAIT 狀態(tài)的連接會(huì)消失妓蛮,然后產(chǎn)生一個(gè)新的 ESTABLISHED 狀態(tài)的連接怠李。

走到這里其實(shí)會(huì)發(fā)現(xiàn)兩個(gè)問(wèn)題:

  1. 為什么即使不停止 hugegraph-server 也會(huì)產(chǎn)生 CLOSE_WAIT 狀態(tài)的連接?
  2. 為什么 CLOSE_WAIT 狀態(tài)的連接不會(huì)自己消失蛤克,而是要等到 studio 再請(qǐng)求一次才會(huì)消失捺癞?

Step 3

要解釋第一個(gè)問(wèn)題,得找到 TCP 連接關(guān)閉的四次握手時(shí)序圖

image.png

可以看到构挤,連接關(guān)閉中只有被動(dòng)關(guān)閉的一方才會(huì)出現(xiàn) CLOSE_WAIT 狀態(tài)髓介,所以肯定是 hugegraph-server 主動(dòng)關(guān)閉了連接。

然后發(fā)現(xiàn) RestServer 有一個(gè) KeepAlive IdleTimeout筋现,閑置超過(guò)此時(shí)間的連接會(huì)被 jersey 關(guān)閉唐础。

The time in seconds to keep an inactive connection alive.

這個(gè)參數(shù)的默認(rèn)值為 30 秒,我們將其修改為 60矾飞,驗(yàn)證確實(shí)符合預(yù)期一膨。

這里插一句題外話,KeepAlive 除了有 IdleTimeout 參數(shù)洒沦,還有一個(gè) Max Requests Count豹绪。

The max number of HTTP requests allowed to be processed on one keep-alive connection.

這個(gè)參數(shù)的意思是:當(dāng)一個(gè)連接處理的請(qǐng)求數(shù)超過(guò)該值了,就將其關(guān)閉申眼。默認(rèn)為 256瞒津,我們將其修改為 5蝉衣,但是經(jīng)過(guò)調(diào)試發(fā)現(xiàn),在處理完第 6 個(gè)請(qǐng)求之后才會(huì)關(guān)閉連接巷蚪,
而不是我們?cè)O(shè)置的 5买乃。調(diào)試代碼:

private boolean checkKeepAliveRequestsCount(final HttpContext httpContext) {
    if (!allowKeepAlive) {
        return false;
    }
    
    final KeepAliveContext keepAliveContext = keepAliveContextAttr.get(httpContext);
    final int requestsProcessed = keepAliveContext.requestsProcessed++;
    final int maxRequestCount = keepAlive.getMaxRequestsCount();
    final boolean isKeepAlive = (maxRequestCount == -1 ||
            keepAliveContext.requestsProcessed <= maxRequestCount);
    
    if (requestsProcessed == 0) {
        if (isKeepAlive) { // New keep-alive connection
            KeepAlive.notifyProbesConnectionAccepted(keepAlive,
                    keepAliveContext.connection);
        } else { // Refused keep-alive connection
            KeepAlive.notifyProbesRefused(keepAlive, keepAliveContext.connection);
        }
    }

    return isKeepAlive;
}

關(guān)鍵在于keepAliveContext.requestsProcessed <= maxRequestCount,當(dāng)處理完第一個(gè)請(qǐng)求钓辆,keepAliveContext.requestsProcessed的值為1,
第二個(gè)請(qǐng)求值為2 ... 第五個(gè)請(qǐng)求為5肴焊,仍然是滿足 <= 5 條件的前联,這時(shí)仍然認(rèn)為這個(gè)連接是 KeepAlive 的,直到處理完第六個(gè)才不滿足條件娶眷,才會(huì)進(jìn)行關(guān)閉連接的操作似嗤。

我覺得這是一個(gè) BUG,至少這個(gè)參數(shù)的說(shuō)明與表現(xiàn)不符届宠。但是找了半天也沒(méi)找到該往 github 的哪個(gè)倉(cāng)庫(kù)反饋烁落。

Step 4

為什么 CLOSE_WAIT 狀態(tài)的連接不會(huì)自己消失,而是要等到 studio 再請(qǐng)求一次才會(huì)消失豌注?

這里得跟蹤 HttpClient 的代碼伤塌,我們以服務(wù)端關(guān)閉了連接,studio 的連接變成了 CLOSE_WAIT 之后作為調(diào)試起始點(diǎn)轧铁,在PoolingHttpClientConnectionManager
AbstractConnPool中獲取連接和關(guān)閉連接的代碼處加了很多斷點(diǎn)每聪,經(jīng)過(guò)一番折騰,終于發(fā)現(xiàn)齿风,在AbstractConnPoolgetPoolEntryBlocking方法中找到了第二個(gè)
問(wèn)題的答案药薯。下面給出關(guān)鍵代碼:

private E getPoolEntryBlocking() {

    ...
    while (entry == null) {
        ...

        else if (this.validateAfterInactivity > 0) {
            if (entry.getUpdated() + this.validateAfterInactivity <= System.currentTimeMillis()) {
                if (!validate(entry)) {
                    entry.close();
                }
            }
        }
        ...
    }
    ...
}

if (!validate(entry))一直往里跟會(huì)走到AbstractHttpClientConnectionisStale()方法,該方法會(huì)嘗試從socketInputBuffer中讀一點(diǎn)數(shù)據(jù)救斑,
讀不到就認(rèn)為stale童本,然后validate(entry)驗(yàn)證失敗,調(diào)用entry.close()將連接關(guān)閉脸候。

說(shuō)到這里基本上已經(jīng)弄明白了上面這兩個(gè)問(wèn)題穷娱,但其實(shí)還有一個(gè)問(wèn)題不太清楚。

  1. 服務(wù)端主動(dòng)關(guān)閉連接導(dǎo)致客戶端處于 CLOSE_WAIT 狀態(tài)纪他,但是根據(jù)四次握手的流程圖鄙煤,為什么服務(wù)端沒(méi)有卡在 FIN-WAIT1 的狀態(tài),而是直接就消失了茶袒,就好像是正常關(guān)閉了一樣梯刚。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市薪寓,隨后出現(xiàn)的幾起案子亡资,更是在濱河造成了極大的恐慌澜共,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锥腻,死亡現(xiàn)場(chǎng)離奇詭異嗦董,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瘦黑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門京革,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人幸斥,你說(shuō)我怎么就攤上這事匹摇。” “怎么了甲葬?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵廊勃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我经窖,道長(zhǎng)坡垫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任画侣,我火速辦了婚禮冰悠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘配乱。我一直安慰自己屿脐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布宪卿。 她就那樣靜靜地躺著的诵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佑钾。 梳的紋絲不亂的頭發(fā)上西疤,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音休溶,去河邊找鬼代赁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛兽掰,可吹牛的內(nèi)容都是我干的芭碍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼孽尽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窖壕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瞻讽,失蹤者是張志新(化名)和其女友劉穎鸳吸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體速勇,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晌砾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烦磁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片养匈。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖都伪,靈堂內(nèi)的尸體忽然破棺而出乖寒,到底是詐尸還是另有隱情,我是刑警寧澤院溺,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站磅轻,受9級(jí)特大地震影響珍逸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜聋溜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一谆膳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撮躁,春花似錦漱病、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嗤军,卻和暖如春注盈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叙赚。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工老客, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人震叮。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓胧砰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親苇瓣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尉间,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344