k8s環(huán)境下應(yīng)用句柄數(shù)過大的原因定位

背景

運(yùn)維發(fā)現(xiàn)部署在k8s環(huán)境上的應(yīng)用A比在環(huán)境E中的句柄數(shù)要多十倍. 讓我協(xié)助排查下.

排查過程

初步分析

通過lsof 命令看了下進(jìn)程打開的文件句柄,發(fā)現(xiàn)其中pipe出現(xiàn)了很多次,大概1300次, 而相對(duì)應(yīng)的stable環(huán)境中的應(yīng)用A打開的pipe為130次

問題:

為什么會(huì)有這么多pipe,是誰(shuí)創(chuàng)建的叮贩,為什么要?jiǎng)?chuàng)建?

我并不是很熟悉java中涉及pipe的場(chǎng)景璧疗,所以根本就不知道誰(shuí)會(huì)創(chuàng)建pipe酷愧,只能先找到所有會(huì)使用pipe的地方箕昭,然后在進(jìn)一步分析.

如何找到所有使用pipe的地方歌憨?

想想貌似只能攔截下pipe的調(diào)用叶雹,一旦發(fā)現(xiàn)有進(jìn)程調(diào)pipe财饥,就記錄下調(diào)用棧,通過這樣的方式應(yīng)該就可以收集到部分pipe的使用場(chǎng)景了.

由于不知道是哪些java的api會(huì)調(diào)用pipe操作折晦,在java層面進(jìn)行攔截是不可能的了钥星,只能在系統(tǒng)層進(jìn)行嘗試了.

鑒于pipe是一個(gè)system call, 可以考慮通過strace -e trace=pipe 進(jìn)行跟蹤满着。

不過由于strace僅僅是負(fù)責(zé)跟蹤谦炒,在我們拿到相應(yīng)進(jìn)程號(hào)之后再去查看調(diào)用棧的時(shí)候可能已經(jīng)太遲了。 所以該方法沒啥用.

可能的思路

能夠攔截特定調(diào)用风喇,在攔截到后又能夠執(zhí)行特定action的工具宁改,聽說過的有以下幾種:

  1. dtrace

    不熟悉,看了下文檔用起來(lái)很麻煩的樣子

  2. 使用linux LD_PRELOAD機(jī)制增強(qiáng)pipe調(diào)用.

    只是個(gè)思路,或許可行

  3. gdb

    相對(duì)熟悉一些,決定用這個(gè).

gdb的排查步驟

  1. 使用gdb啟動(dòng)應(yīng)用

    gdb --args java /var/www/xxx/target/XXXX.jar
    
  2. 攔截pipe的斷點(diǎn)

    (gdb) catch syscall pipe
    
    
  3. 忽略SIGSEGV信號(hào)

    (gdb) handle SIGSEGV nostop noprint pass
    

    為什么要做這個(gè)?看這個(gè) : https://neugens.wordpress.com/2015/02/26/debugging-the-jdk-with-gdb/

  4. 執(zhí)行程序

    (gdb) run
    當(dāng)java程序調(diào)用pipe時(shí)魂莫,程序就會(huì)暫停下來(lái)还蹲,等待用戶指令.
    
    
  5. 等待捕獲pipe調(diào)用事件

  6. 捕獲pipe調(diào)用事件

    Catchpoint 1 (returned from syscall 'pipe'), 0x00007fd22237ff07 in pipe () at ../sysdeps/unix/syscall-template.S:82
    82  T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
    
    
  7. 執(zhí)行info thread 查看下當(dāng)前的線程如下:

    
    3 Thread 0x7fd2203b7700 (LWP 1630)  pthread_cond_wait@@GLIBC_2.3.2 () at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:183
    * 2 Thread 0x7fd222e81700 (LWP 1605)  0x00007fd22237ff07 in pipe () at ../sysdeps/unix/syscall-template.S:82
    1 Thread 0x7fd222e83700 (LWP 1522)  0x00007fd222a5a2fd in pthread_join (threadid=140540505495296, thread_return=0x7ffeaef13e00) at pthread_join.c:89
    
    

    可以看到線程2是活的。記住1605這個(gè)線程號(hào).

  8. 執(zhí)行g(shù)core dump下當(dāng)前的內(nèi)存鏡像

    (gdb) gcore
    Saved corefile core.1522
    

    為啥要gcore ,因?yàn)樵谀J(rèn)的gdb環(huán)境下耙考,看不到j(luò)ava的調(diào)用棧. 不過有人做了腳本支持在gdb下支持查看java的調(diào)用棧谜喊,詳見http://mail.openjdk.java.net/pipermail/jdk9-dev/2016-May/004379.html

  9. 使用 jstack java core.1522 解析core文件中的調(diào)用棧.

    Thread 1605: (state = IN_NATIVE)
    - sun.nio.ch.IOUtil.makePipe(boolean) @bci=0 (Interpreted frame)
    - sun.nio.ch.EPollSelectorImpl.<init>(java.nio.channels.spi.SelectorProvider) @bci=27, line=65 (Interpreted frame)
    - sun.nio.ch.EPollSelectorProvider.openSelector() @bci=5, line=36 (Interpreted frame)
    - io.netty.channel.nio.NioEventLoop.openSelector() @bci=4, line=174 (Interpreted frame)
    - io.netty.channel.nio.NioEventLoop.<init>(io.netty.channel.nio.NioEventLoopGroup, java.util.concurrent.ThreadFactory, java.nio.channels.spi.SelectorProvider, io.netty.channel.SelectStrategy, io.netty.util.concurrent.RejectedExecutionHandler) @bci=88, line=150 (Interpreted frame)
    - io.netty.channel.nio.NioEventLoopGroup.newChild(java.util.concurrent.ThreadFactory, java.lang.Object[]) @bci=29, line=103 (Interpreted frame)
    - io.netty.util.concurrent.MultithreadEventExecutorGroup.<init>(int, java.util.concurrent.ThreadFactory, java.lang.Object[]) @bci=146, line=64 (Interpreted frame)
    - io.netty.channel.MultithreadEventLoopGroup.<init>(int, java.util.concurrent.ThreadFactory, java.lang.Object[]) @bci=14, line=50 (Interpreted frame)
    - io.netty.channel.nio.NioEventLoopGroup.<init>(int, java.util.concurrent.ThreadFactory, java.nio.channels.spi.SelectorProvider, io.netty.channel.SelectStrategyFactory) @bci=22, line=70 (Interpreted frame)
    - io.netty.channel.nio.NioEventLoopGroup.<init>(int, java.util.concurrent.ThreadFactory, java.nio.channels.spi.SelectorProvider) @bci=7, line=65 (Interpreted frame)
    - io.netty.channel.nio.NioEventLoopGroup.<init>(int, java.util.concurrent.ThreadFactory) @bci=6, line=56 (Interpreted frame)
    - org.asynchttpclient.netty.channel.ChannelManager.<init>(org.asynchttpclient.AsyncHttpClientConfig, io.netty.util.Timer) @bci=394, line=173 (Interpreted frame)
    - org.asynchttpclient.DefaultAsyncHttpClient.<init>(org.asynchttpclient.AsyncHttpClientConfig) @bci=73, line=85 (Interpreted frame)
    ...
    

    可以看到1605線程的調(diào)用棧如上. (此時(shí)看到了java層面調(diào)用pipe的地方,也許是java中唯一與pipe發(fā)生交互的地方倦始,也許不是,繼續(xù)采集幾個(gè)樣本看看...).

  10. 執(zhí)行cont,不一會(huì)又會(huì)捕捉到pipe調(diào)用事件, 重復(fù)8,9 步幾次.

    觀察到幾乎所有的pipe操作都是sun.nio.ch.IOUtil.makePipe 觸發(fā)的斗遏,而上游都由Netty的NioEventLoopGroup觸發(fā).

NioEventLoopGroup代碼分析

翻了下NioEventLoopGroup的代碼,有如下一段:


MultithreadEventLoopGroup.java

protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
        ...
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            try {
                children[i] = newChild(threadFactory, args);// 這里每次調(diào)用創(chuàng)建一個(gè)pipe。 如果循環(huán)很大的話楣号,是可能創(chuàng)建很多pipe的.
                success = true;
            }
        }
        ...
} 

查看下nThreads的賦值邏輯如下:

如果調(diào)用方有指定最易,則使用指定值怒坯,否則為cpu數(shù)*2. 代碼如下

MultithreadEventLoopGroup.java

static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}

結(jié)論

從代碼的分析結(jié)果來(lái)看,懷疑是k8s環(huán)境中cpu核數(shù)過多導(dǎo)致藻懒。
查看k8s環(huán)境中應(yīng)用所在容器的cpu數(shù)剔猿,為20是環(huán)境E中cpu數(shù)的10倍。
至此, k8s環(huán)境中句柄數(shù)過大的原因算是清楚啦.

遺留問題

  1. 為什么用epoll的時(shí)候會(huì)觸發(fā)pipe的創(chuàng)建?
    Java 中的Selector 同時(shí)定義了select和wakeup兩個(gè)方法.
    當(dāng)線程阻塞在select()方法時(shí)嬉荆,可以通過wakeup方法進(jìn)行喚醒.
    在Linux下, 系統(tǒng)底層是沒有提供任何喚醒機(jī)制的. java在實(shí)現(xiàn)時(shí)使用了一個(gè)技巧归敬,即創(chuàng)建一個(gè)管道,將管道的讀事件注冊(cè)到epoll中鄙早, wakeup方法中向管道寫入一字節(jié)的數(shù)據(jù)來(lái)觸發(fā)epoll的返回.
    相關(guān)代碼:
    EPollSelectorImpl.java

參考資料

https://openresty.org/posts/dynamic-tracing/](https://openresty.org/posts/dynamic-tracing
https://www-zeuthen.desy.de/unix/unixguide/infohtml/gdb/Set-Catchpoints.html#Set-Catchpoints

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汪茧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子限番,更是在濱河造成了極大的恐慌舱污,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥虐,死亡現(xiàn)場(chǎng)離奇詭異扩灯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)霜瘪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門珠插,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人颖对,你說我怎么就攤上這事捻撑。” “怎么了缤底?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵顾患,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我个唧,道長(zhǎng)描验,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任坑鱼,我火速辦了婚禮,結(jié)果婚禮上絮缅,老公的妹妹穿的比我還像新娘鲁沥。我一直安慰自己,他們只是感情好耕魄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布画恰。 她就那樣靜靜地躺著,像睡著了一般吸奴。 火紅的嫁衣襯著肌膚如雪允扇。 梳的紋絲不亂的頭發(fā)上缠局,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音考润,去河邊找鬼狭园。 笑死,一個(gè)胖子當(dāng)著我的面吹牛糊治,可吹牛的內(nèi)容都是我干的唱矛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼井辜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼绎谦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起粥脚,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤窃肠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后刷允,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冤留,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年恃锉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搀菩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡破托,死狀恐怖肪跋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情土砂,我是刑警寧澤州既,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站萝映,受9級(jí)特大地震影響吴叶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜序臂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一蚌卤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奥秆,春花似錦逊彭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至悼瘾,卻和暖如春囊榜,著一層夾襖步出監(jiān)牢的瞬間审胸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工卸勺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留砂沛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓孔庭,卻偏偏與公主長(zhǎng)得像尺上,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子圆到,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容