Epoll模型的高性能服务器丢失数据问题解决


2019.3.3更新

经过测试,使用epoll的ET模式对比LT模式并没有太大的性能提升,但是LT模式的可靠性远远优于ET模式,如果使用ET模式需要增加大量代码以提升可靠性。所以在设计系统的时候应对两种模式加以权衡,需要极致性能则需要解决ET不够可靠的弊端,或者直接选用LT模式牺牲部分性能。


0. 问题起因

使用epoll模型后,虽然提高了服务器的并发性,但是在测试时发现一个很严重的问题,当服务器负载很高时,部分数据会丢弃掉,问题最严重的时候10万条测试数据可以达到百分之5的丢失概率,这对于服务器来说已经违背的初衷,虽然并发性能提高了,但是服务器的可靠性得不到保证。

1. 问题分析

通过Epoll部分的代码,查阅了Epoll的相关资料和特性,做出如下推测。
首先从Epoll的两种事件模型说起。
Edge Triggered(ET)
Level Triggered(LT)
当使用LT模式时,事件发生时内核会通知程序处理该事件(例如可读事件),如果用户没有做出响应,内核会继续通知。这样是造成了LT模式较慢的原因,但是LT模式可以减少编程时错误的可能性。
为了提高服务器的并发性,使用ET模式,当事件发生时内核同样会通知程序处理该事件,但此时内核默认你已经处理的该事件,不管你是不是真的处理了,都不会再通知你。
在此次高性能服务器设计中,三个线程分别执行读操作,测试客户端持续向服务器发送数据,维持服务器高负载。数据到来时内核可能通知程序处理数据,但线程分配调度时可能还未及时处理数据,新的数据已经到达,此时内核再次通知程序,程序收到通知后,只处理了最新一次的数据,造成之前的数据累计后丢失。

2. 改进服务器

  1. 通过while循环执行recv函数,一直读到出错,然后处理返回信息
  2. recv第四个参数设置非阻塞,无论是否读到数据立即返回
  3. 通过errno和返回值判断已经完成接收或其他错误(例子中只处理的缓冲区无数据的情况)
/// ET模式一直读取防止数据丢失
while((readCnt = recv(connfd, m_readBuf, 1024, MSG_DONTWAIT)) > 0)
{
    DealNode *node = new DealNode(connfd, m_readBuf);
    lock_guard t(StaticManagement::getInstance()->getDealLock(m_index));
    StaticManagement::getInstance()->getDealQueue(m_index)->push(node);
}

错误处理如下

/// 对端断开连接或读取到空数据
if(readCnt == 0)
{
	if(errno == EAGAIN)
	{
		return;
	}
	close(connfd);
	cout << "disconnect connfd: " << connfd << endl;
	return;
}

/// 处理错误
if(errno == ECONNREFUSED)
{
	close(connfd);
}

如果读到字节数为0,且errno标志设置为EAGAIN(接收超时),则结束此次读取。
另外附上recv的返回值

返回值 含义
大于0 正常读取到的字节数
等于0 对端断开连接
小于0 出错并设置errno标志

errno标志代表的含义如下

标志 含义
EAGAIN 套接字已标记为非阻塞,但接收操作时被阻塞 或者 接收超时
EBADF sock不是有效的描述词
ECONNREFUSE 远程主机阻绝网络连接
EFAULT 内存空间访问出错
EINTR 操作被信号中断
EINVAL 参数无效
ENOMEM 内存不足
ENOTCONN 与面向连接关联的套接字尚未被连接
ENOTSOCK sock索引的不是套接字 当返回值为0时,为正常关闭连接

你可能感兴趣的:(高性能服务器,epoll,高性能服务器,linux)