美文网首页程序员
k8s环境下应用句柄数过大的原因定位

k8s环境下应用句柄数过大的原因定位

作者: 尘埃观世界 | 来源:发表于2018-07-24 22:22 被阅读0次

背景

运维发现部署在k8s环境上的应用A比在环境E中的句柄数要多十倍. 让我协助排查下.

排查过程

初步分析

通过lsof 命令看了下进程打开的文件句柄,发现其中pipe出现了很多次,大概1300次, 而相对应的stable环境中的应用A打开的pipe为130次

问题:

为什么会有这么多pipe,是谁创建的,为什么要创建?

我并不是很熟悉java中涉及pipe的场景,所以根本就不知道谁会创建pipe,只能先找到所有会使用pipe的地方,然后在进一步分析.

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

想想貌似只能拦截下pipe的调用,一旦发现有进程调pipe,就记录下调用栈,通过这样的方式应该就可以收集到部分pipe的使用场景了.

由于不知道是哪些java的api会调用pipe操作,在java层面进行拦截是不可能的了,只能在系统层进行尝试了.

鉴于pipe是一个system call, 可以考虑通过strace -e trace=pipe 进行跟踪。

不过由于strace仅仅是负责跟踪,在我们拿到相应进程号之后再去查看调用栈的时候可能已经太迟了。 所以该方法没啥用.

可能的思路

能够拦截特定调用,在拦截到后又能够执行特定action的工具,听说过的有以下几种:

  1. dtrace

    不熟悉,看了下文档用起来很麻烦的样子

  2. 使用linux LD_PRELOAD机制增强pipe调用.

    只是个思路,或许可行

  3. gdb

    相对熟悉一些,决定用这个.

gdb的排查步骤

  1. 使用gdb启动应用

    gdb --args java /var/www/xxx/target/XXXX.jar
    
  2. 拦截pipe的断点

    (gdb) catch syscall pipe
    
    
  3. 忽略SIGSEGV信号

    (gdb) handle SIGSEGV nostop noprint pass
    

    为什么要做这个?看这个 : https://neugens.wordpress.com/2015/02/26/debugging-the-jdk-with-gdb/

  4. 执行程序

    (gdb) run
    当java程序调用pipe时,程序就会暂停下来,等待用户指令.
    
    
  5. 等待捕获pipe调用事件

  6. 捕获pipe调用事件

    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. 执行info thread 查看下当前的线程如下:

    
    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这个线程号.

  8. 执行gcore dump下当前的内存镜像

    (gdb) gcore
    Saved corefile core.1522
    

    为啥要gcore ,因为在默认的gdb环境下,看不到java的调用栈. 不过有人做了脚本支持在gdb下支持查看java的调用栈,详见http://mail.openjdk.java.net/pipermail/jdk9-dev/2016-May/004379.html

  9. 使用 jstack java core.1522 解析core文件中的调用栈.

    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线程的调用栈如上. (此时看到了java层面调用pipe的地方,也许是java中唯一与pipe发生交互的地方,也许不是,继续采集几个样本看看...).

  10. 执行cont,不一会又会捕捉到pipe调用事件, 重复8,9 步几次.

    观察到几乎所有的pipe操作都是sun.nio.ch.IOUtil.makePipe 触发的,而上游都由Netty的NioEventLoopGroup触发.

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);// 这里每次调用创建一个pipe。 如果循环很大的话,是可能创建很多pipe的.
                success = true;
            }
        }
        ...
} 

查看下nThreads的赋值逻辑如下:

如果调用方有指定,则使用指定值,否则为cpu数*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);
    }
}

结论

从代码的分析结果来看,怀疑是k8s环境中cpu核数过多导致。
查看k8s环境中应用所在容器的cpu数,为20是环境E中cpu数的10倍。
至此, k8s环境中句柄数过大的原因算是清楚啦.

遗留问题

  1. 为什么用epoll的时候会触发pipe的创建?
    Java 中的Selector 同时定义了select和wakeup两个方法.
    当线程阻塞在select()方法时,可以通过wakeup方法进行唤醒.
    在Linux下, 系统底层是没有提供任何唤醒机制的. java在实现时使用了一个技巧,即创建一个管道,将管道的读事件注册到epoll中, wakeup方法中向管道写入一字节的数据来触发epoll的返回.
    相关代码:
    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

相关文章

  • k8s环境下应用句柄数过大的原因定位

    背景 运维发现部署在k8s环境上的应用A比在环境E中的句柄数要多十倍. 让我协助排查下. 排查过程 初步分析 通过...

  • 一分钟处理应用打开文件句柄数太多的问题

    遇到的问题 压力测试环境,发现某应用的线程数和打开文件句柄数持续飙高,并且不释放。 文件句柄打开数-已超过系统阈值...

  • websocket-bench测试websocket记录

    测试性能: 需要安装node环境 websocket-bench 安装: 查看系统句柄数设置 查看当前系统句柄数使...

  • k8s二进制集群搭建

    Kubernetes(K8s)-k8s服务集群安装 一、环境准备 1、机器环境 节点CPU核数必须是 :>= 2核...

  • Nginx 30421#0: accept4() failed

    报错信息: 1,根据排查很容易了解到是文件连接数太多了,网上很多教程都是叫你去修改句柄数但是查了下我的句柄数有6万...

  • 对象访问定位

    对象访问定位 句柄 在Java堆划分一块内存作为句柄池,reference中存储的对象的句柄地址,句柄中包含对象实...

  • java

    句柄 参考 java句柄 什么是句柄句柄(Handle)来标示应用程序中不同的对象和同类中不同的实例注意:句柄有人...

  • nginx如何支持百万连接

    由于每个连接都需要占用一个文件句柄,而很多linux的默认文件句柄数为1024,要达到百万连接需要先修改文件句柄数...

  • 有关句柄和指针的常用函数

    有关句柄和指针的常用函数 1. 如何获取应用程序的 实例句柄?AfxGetInstanceHandle() 应用程...

  • k8s 如何让你的应用活的更久

    众所周知,k8s 可以托管你的服务 / 应用,当出现各种原因导致你的应用挂掉之后,k8s 会自动重启你的服务,那么...

网友评论

    本文标题:k8s环境下应用句柄数过大的原因定位

    本文链接:https://www.haomeiwen.com/subject/usqumftx.html