很久没写文章了,主要自己还在沉淀,学习类的分享总觉得为了分享而分享,多几天可能自己都记不清细节了,所以一直没有再去写,这次遇到一个比较有意思的bug,多路复用的一个bug,这个领域那,虽然自己也学习过,但是一直也没写过代码练习,就这个机会就一并练习下,可能对高手来说这是稀松平常的问题,却耗费了我们一天左右的时间进行问题的排查。,我们有个跑了很久的c开发的系统,在新版本测试中,发现一直会core,core的位置飘忽不定,而且core的有点莫名其妙,根本不该core的地方却core了,开始从现象看来很像是多线程引起的问题,排查了下却没有发现问题所在。,由于代码量很多,我们排查步骤是:,利用ascan库定位core的位置,我们根据core的地方开始关闭相关的功能。,减少了core的地方后,接下来还是会core,core的位置在一个unix socket 通信线程的创建上,这个线程本该早就创建好的,但是为什么运行5-10分钟才开始创建,线程创建没有做父子进程的监控,所以不存在重启可能而且如果是这个线程挂了,引起的重新创建也是不可能的,因为线程挂了,必然会导致进程都挂了,结果整个进程的其他线程仍然是正常运行的。(这个至今无解),由于是线程创建问题,同事注意到了此进程的由于新增写kafka的功能,导致线程过多,遂代码上注释掉这些功能,继续排查。,由于这个线程主要用来执行一些程序交互命令的,所以就用客户端工具连着去测试,发现经常连不上,有时候连上也会core,ascan的报错信息:,从报错信息利用add2line命令查到具体的堆栈,这个命令以前文章有聊过,执行起来是:,如是经过gdb调试,发现core的时候在unix socket的处理函数的返回上,也就是说栈信息被破坏了,百思不得其解啊,甚至汇编每次跟踪地址也没查到谁破坏的。,正常连接的时候,客户端进程卡死,通过strace 跟踪客户端的系统调用,如下:,通过客户端的日志打印信息,发送command-list命令后服务器端没有返回,sendto命令是成功的,返回28,来看看服务器端怎么说:,通过服务器端的日志来看,只收到了初次的版本信息,后续的command-list命令并没有收到。 这就很奇怪了。,
,交互图,百思不得其解,是难道是内核bug?通过gdb调试并没有发现什么问题,接着通过lsof 查看socket文件的连接数,当我们通过客户端去连接的时候,连接数递增了,这没啥问题,如下图:,开始没有注意到这个1172,这个文件描述符有什么特别的地方,也知道select做多路复用的时候,有一定的局限,只能处理1024个连接,我在想,我们就只有一个连接没有超过1024这个限制啊, 也许有朋友知道了原因,是1172超过了1024,也就是说select的FD的数量不能超过1024,且大小也不能超过,那么就是这么简单嘛,继续实践吧。,在高性能的服务器上,多采用多路复用技术,多路其实就是多个连接,复用就是复用此服务器进程,那么何在一起多路复用,就是用一个进程进行多个连接的处理。,对于服务器来说,开放端口等待客户端连接,开始多采用多进程或多线程编程的方式,即每个连接采用单独的进程或线程进行处理,但是每台计算机因为内存等资源限制,可以开的进程或线程数有限,而且过多的线程会导致线程切换的成本过大,缓存失效等一系列问题,根本无法做到单机处理十万、百万连接。,如果采用非阻塞,在用户进程里面轮询方式那?这样会占用很高的cpu资源,所以后来发展出多路复用技术,即采用一个进程处理多个连接,一个引用怎么处理多个连接那,不可能采用阻塞的方式,一旦阻塞在一个连接的IO上,其他连接有事件过来了也没办法处理,那只能轮询查看各个连接上是否有可读、可写消息,从而达到多路复用的目的,linux内核提供select、poll、epoll三种多路复用机制。,3.1.1 基本使用说明,1.nfds 表示监视的文件描述符中,待测的最大描述符+1. 2. readfds:监视有读数据到达的文件描述符集合。 3. writefds:监视有写数据到达的文件描述符集合。 4. exceptfds:监视有异常发生的文件描述符集合。 这三个集合每次都要传入,每当要监视的事件发生时候,都会被复制出来。 5. timeout 设置为NULL,则select阻塞,直到事件发生;如果不为NULL,且值不为0,则等待固定时间,如果这个事件没有监视事件来的话,也仍然会返回;如果不为NULL,且值为0,则不等待,立刻返回。,下面四个为宏,含义如后面的注释,在linux内核的 中的实现如下(不同的版本实现稍微有差异):,fd_set是由unsigned long 的类型组构成的位图, FD_SET 操作即找到哪个unsigned long的哪个位,通过((__fd_mask) 1 << ((d) % __NFDBITS)) 来定位具体的位信息,将那一位设置为1,取反即设置为0.,问题出在是FD_SET地方,即在__FD_ELT(d) ((d) / __NFDBITS) 如果d的值大于1024,那么fds_bits 就越界了,就会破坏栈数据,从而导致返回异常。,简化下我们的程序,写的如下:,客户端连接代码:,练习代码,写的比较挫,客户端通过unix socket 连接到服务器,然后接收用户输入发送给服务器,服务器回送消息,直到用户输入quit退出。 示意下效果:,客户端显示:,3.1.2 core模拟,在main的开始位置加上如下的代码:,会发现,程序会自动退出或core,偶尔也有成功的情况,还有的情况是发送到的命令没回复,也就是没监听起来。,3.2 select 缺点,虽然select也支持了IO多路复用,但是存在以下问题:,每次select返回后,监视的集合需要重新设置,比较麻烦。,限制1024个连接,如果想在应用上突破连接,采用malloc等动态申请内存方式也是可以,但是最好采用poll或epoll。,每次都要将监视的文件描述符复制到内核空间,有事件的发生的时候,需要再从内核空间复制到用户空间,比较占用cpu资源, 几种机制的性能比较如下
© 版权声明
文章版权归作者所有,未经允许请勿转载。