首页 > TCP/IP > 网络服务器由于文件连接数不够而导致listen sock总是可读CPU跑满

网络服务器由于文件连接数不够而导致listen sock总是可读CPU跑满

2014年3月30日 发表评论 阅读评论 7156次阅读    

前几天碰到碰到一个线上redis CPU跑满的情况,基本无法处理正常请求了,刚开始以为是其他地方的问题,后来grep "Max open files" /proc/`pidof redis-server`/ -r  排查原来是启动redis的时候。ulimit -n 只有1024,从而无法接受新连接。

晚高峰时段段时间突发的大量请求导致某个时候redis连接数超过1024,从而listen sock 持续可读并且accept失败,从而CPU跑满,进而导致更严重的雪崩。

之前在写网络程序的时候也遇到过这种情况,比如简单复现的话,ulimit -n 20 修改当前会话的打开文件数,然后启动某个服务器程序,然后给其发送超过限制的TCP连接,这时候监听套接字一定会每次select / epoll的时候,都返回句柄可读。

从而不断的accept调用,然后accept立即出现如下错误,也就是EMFILE:

accept failed. errno:24, errmsg:Too many open files.

但是,accept的实现里面遇到句柄数不够的处理方法为:留在下次处理,而不是断开TCP连接,也是有道理的,因为下回说不定就关闭了一些呢。

但这一就会导致监听套接字不断有可读消息,但却accept无法接受,从而listen的backlog被塞满;从而导致后面的连接被RST了。

这里多啰嗦一下,memcached对于这种情况的处理有点特殊,或者说周到,如果memcache accept 的时候返回EMFILE,那么它会立即调用listen(sfd, 0) , 也就是将监听套接字的等待accept队列的backlog设置为0,从而拒绝掉这部分请求,减轻系统负载,保全自我。还是挺不错的。

static void drive_machine(conn *c) {
#else
            sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
#endif
            if (sfd == -1) {
                if (use_accept4 && errno == ENOSYS) {
                    use_accept4 = 0;
                    continue;
                }
                perror(use_accept4 ? "accept4()" : "accept()");
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    /* these are transient, so don't log anything */
                    stop = true;
                } else if (errno == EMFILE) {
                    if (settings.verbose > 0)
                        fprintf(stderr, "Too many open connections\n");
                    accept_new_conns(false);
                    stop = true;
                } else {
                    perror("accept()");
                    stop = true;
                }
                break;
            }
}

上面accept出现EMFILE错误会调用accept_new_conns,并且设置stop=true,也就是会停止处理后面的事件,在accept_new_conns里面会停掉整个LISTEN socket的epoll事件:

/*
 * Sets whether we are listening for new connections or not.
 */
void do_accept_new_conns(const bool do_accept) {
    conn *next;

    for (next = listen_conn; next; next = next->next) {
        if (do_accept) {
            update_event(next, EV_READ | EV_PERSIST);
            if (listen(next->sfd, settings.backlog) != 0) {
                perror("listen");
            }
        }
        else {
            update_event(next, 0);
            if (listen(next->sfd, 0) != 0) {
                perror("listen");
            }
        }
    }

    if (do_accept) {
        STATS_LOCK();
        stats.accepting_conns = true;
        STATS_UNLOCK();
    } else {
        STATS_LOCK();
        stats.accepting_conns = false;
        stats.listen_disabled_num++;
        STATS_UNLOCK();
        allow_new_conns = false;
        maxconns_handler(-42, 0, 0);
    }
}

可以看出如果出错,会调用 listen(next->sfd, 0),设置backlog参数为0,也就是不接受任何新客户端请求,一致返回ECONNREFUSED拒绝新连接。
并且为了让系统能够自动的恢复重新接受新连接,设置了一个maxconns_handler的定时器用来恢复功能。

memcached通过这个来避免了连接数满时的系统繁忙问题。

Share
分类: TCP/IP 标签: , ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。