记录Libevent延迟读回调问题的发现和解决

来源:互联网 发布:帝国cms和苹果cms 编辑:程序博客网 时间:2024/05/21 22:04

背景描述:       

       最近做一个Mini项目,实现包括语音的及时通讯App,我负责后台的开发。底层的网络收发框架采用的是Libevent的bufferevent,是一个基于事件驱动的高效异步I/O框架,接口简单易懂,采用linux内核的Epoll实现网络事件触发。经过封装用在项目中,在和终端测试时出现问题:终端发送的Request包,Server回应的Response包居然延迟30s---2min不等。简直不能忍!

问题定位:

       由于服务器架构分为介入层+逻辑层+存储层,每个来自终端的请求包,都要经过接入层的转发,在对应的逻辑层进行处理和响应。延迟的时间是在客户端?接入层?逻辑层?一个请求包从终端发出到接受到对应响应包为整个流程,可分为如下几个时间段:


其中,黑线表示Request包,红线表示Response包。

      经过接入层读包日志分析,排除公网网络传输的1,7延迟。发现2--6出现严重延迟。初步定位问题位于Server内部。继续对比逻辑层读包日志,发现request包从接入层发出,到逻辑层读出出现10s+的延迟,同时response包从逻辑层写出到接入层读出也存在10s+的延迟。难道是底层网络传输3,5 的问题?此刻,linux下抓包利器--Tcpdump出场!读包时,利用日志将收到包的内容以十六进制打印出,找到对应的tcpdump中该包到达的时间---!!!发现同一个包到达Tcp缓冲区后10s+后才被程序读回调出。定位包延迟的根源-- 延迟读回调!

问题原因:

       既然找到问题来源于包的延迟读回调。马上想到底层的网络框架--libevent,其回调机制难道有玄机?Google一下告诉我,libevnet底层用的是Epoll框架,默认是LT水平触发机制。读写回调有四个控制位:读取低水位,读取高水位,写入低水位和写入高水位。但是,各种资料验证,都没有找到延迟读回调的原因。一筹莫展之时,转回头继续从tcpdump中找原因。由于封包采用的是0x01+bufLength+buffer(包二进制序列化)+0x02的形式,在tcpdump中能够定位到每个包的收发,此时发现有多个包一起收发的情况。突然想到会不会是包堆积了?随后进行验证,从日志中制定一个包的读取,反向找到该包在tcpdump到达时间。发现同一时间到达的是两个包,而只有第一个包在到达tcp缓冲的时候被读回调,后面一个包未读出。继续看下一个包tcpdump的到达时间,对应日志一看,原来上一次到达的第二个包此时才被读回调!这是很典型的ET工作模式!而程序中每次读回调只处理一个包,当一次有多个包到达时,堆积的包会导致延迟越来越长。

问题解决:

       解决方法很简单:读回调判断是否还有包在outbuffer里面,若有,则重复一次包处理流程。

总结:

       Libevent的官方文档说法没错,其默认是Epoll的LT模式,而Bufferevent在event上层封装了两个应用层缓冲区evbuffer:input和output。调用者直接在input和output中操作IO数据。每次读回调时,socket的数据是全部读到了input中,但是input若是还有未读完的数据,是不会触发读回调的。从bufferevent来看,是类ET的工作模式的。

       作为编程者而言,无论是LT还是ET模式,每次读回调都应该尽力处理更多的包。即使是LT模式,每次读回调只处理一个包也是很没有效率的做法。总的来说,还是欠缺服务器事件驱动编程经验,经过这次经验教训,对Reactor工作模式有了进一步细致的掌握。共勉之~

1 0
原创粉丝点击