IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    记一次线上Bug的查找过程和思路

    armsword发表于 2016-08-10 14:17:29
    love 0

    前言

    简单说下问题情况,Proxy即作为客户端又有服务端功能,其接受QS(Query Server)的请求,之后向BS(索引服务)发送请求,然后根据BS的返回结果Merge后返回给QS。不能泄露太多东西,所以本文主要是整理一些知识点和问题查找思路。

    问题和现象:

    • Proxy机器上出现大量的CLOSE_WAIT。
    • Proxy失去服务能力,内存使用不为0,CPU使用率为0。
    • 机器报警,TcpListenOverFlows。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    命令查看:
    netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
    结果:
    TIME_WAIT 31927
    CLOSE_WAIT 1570
    ESTABLISHED 40
    TIME_WAIT:表示主动关闭,通过优化系统内核参数可容易解决。
    CLOSE_WAIT:表示被动关闭,需要从程序本身出发。
    ESTABLISHED:表示正在通信
    注:以上数据非真实线上情况,只为举例

    一些知识点

    TCP握手图



    TIME_WAIT:

    Linux系统下,TCP/IP连接断开后,会以TIME_WAIT状态保留一定的时间(2MSL:max segment lifetime),默认为4分钟,然后才会关闭回收资源。当并发请求过多的时候,就会产生大量的 TIME_WAIT状态的连接,无法及时断开的话,会占用大量的端口资源和服务器资源。
    状态保持2MSL的原因:
    1.防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)
    2.可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。
    一些解决方法:
    通过修改/etc/sysctl.conf文件,服务器能够快速回收和重用那些TIME_WAIT的资源 。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
    net.ipv4.tcp_syncookies = 1
    #表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
    net.ipv4.tcp_tw_reuse = 1
    #表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
    net.ipv4.tcp_tw_recycle = 1
    #表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
    net.ipv4.tcp_fin_timeout=30
    生效命令:
    /sbin/sysctl -p

    CLOSE_WAIT:

    CLOSE_WAIT表示被动关闭,出现这种问题,基本是就是客户端连接异常或者自己没有迅速回收资源。
    什么情况下,连接处于CLOSE_WAIT状态呢?

    • 在被动关闭连接情况下,在已经接收到FIN,但是还没有发送自己的FIN的时刻,连接处于CLOSE_WAIT状态。通常来讲,CLOSE_WAIT状态的持续时间应该很短,正如SYN_RCVD状态。但是在一些特殊情况下,就会出现连接长时间处于CLOSE_WAIT状态的情况。
    • 出现大量close_wait的现象,主要原因是某种情况下对方关闭了socket链接,但是我方忙与读或者写,没有关闭连接(CLOSE_WAIT应该是默认2小时才会关闭)。代码需要判断socket,一旦读到0,断开连接,read返回负,检查一下errno,如果不是AGAIN,就断开连接。
    • scoket阻塞,无超时或非阻塞处理。

    EPOLL:

    • EPOLLIN:连接,对端发送普通数据,对端socket正常关闭。
    • EPOLLPRI:带外数据,文件描述符有紧急的数据可读。
    • EPOLLOUT:数据可写。
    • 对端正常关闭(close(),shell下kill或ctr+c),触发EPOLLIN和EPOLLRDHUP,但是不触发EPOLLERR和EPOLLHUP。//重要
    • 对端异常断开连接(如网线),不会触发任何事件。判断方式是向已经断开的socket写或者读,会发生EPOLLERR错误。

    socket接口的几个知识点:

    • read总是在接收缓冲区有数据时立即返回,而不是等到给定的read buffer填满时返回。
      只有当receive buffer为空时,blocking模式才会等待,而nonblock模式下会立即返回-1(errno = EAGAIN或EWOULDBLOCK)
    • blocking的write只有在缓冲区足以放下整个buffer时才返回(与blocking read并不相同)
      nonblock write则是返回能够放下的字节数,之后调用则返回-1(errno = EAGAIN或EWOULDBLOCK)
      对于blocking的write有个特例:当write正阻塞等待时对面关闭了socket,则write则会立即将剩余缓冲区填满并返回所写的字节数,再次调用则write失败(connection reset by peer)

    Bug查找

    通过以上的知识点,我们就可以很好的定位问题原因了,因为Proxy即作为客户端,又有服务端功能,其转发QS的结果到BS之后,再将BS的结果merge后回复给QS。仔细排查代码,发现2处引起Proxy大量出现CLOSE_TIMEWAIT。

    <1>、Proxy将BS的结果merge后,发送QS时,socket是阻塞的,也未做超时处理。

    <2>、QS超时(3S),会主动关闭socket,而Proxy这层没有关闭fd,可能原作者认为会触发EPOLLERR和EPOLLHUP(这里面做了处理)。

    参考链接:

    1、http://www.cnblogs.com/promise6522/archive/2012/03/03/2377935.html


    ——— The End ———





沪ICP备19023445号-2号
友情链接