一、有这么一个程序:
网易邮箱的SMTP流量有一个网关代理程序,叫做smtpproxy。和其它工作在应用层的代理程序没有太大区别,smtpproxy是负责:
等诸多特性的一个程序,由邮件部运维部反垃圾小组研发和维护。
这个smtpproxy程序前后经过了几次大的版本迭代。最初用Python Twisted框架来实现,性能上无法满足高峰期的并发和性能需要,再加上Twisted框架在某些条件下有内存泄露问题,后来用C实现了socket+Linux epoll方式来重构了。尽管epoll很高效(关于epoll请参见另一篇文章《浅谈网络事件的响应机制》),但此时smtpproxy仍只是单进程版本,也就是说只有一个主进程,里面有一个循环不断地处理epoll_wait()就绪的网络事件。在并发量非常高的时候,不能充分利用多核CPU,还会使得一些不幸排队靠后的Socket fd描述符的网络时间处理不及时(没错,epoll也有排队这一说,但和select/poll的队列轮询是不同的概念:当就绪的事件很多时,epoll_wait()函数返回的是这批就绪事件的fd列表,工作进程遍历这个列表来处理网络事件)。这对于SMTP协议来说,很容易导致一些Socket Client超时断开,本次邮件的投递就失败了。
于是,决定将smtpproxy程序从单进程版本改写为多进程版本。随着重构工作的深入,就碰到了传说中的“惊群效应”问题。
二、“惊群”效应:
对于多进程版本,我的程序框架是这样设计的:
先说结果,用C按照这个设计模式实现出了一个多进程版本的smtpproxy,性能的确比单进程版本更高,但却遇到了传说中的“惊群”现象:主进程fork()了N个工作进程,当listen_fd有新的accept()请求过来,操作系统会唤醒全部N个工作进程,因为不同于单进程版本,这N个工作进程都在epoll_wait()同一个listen_fd,操作系统无法判断由谁来accept()这个新连接请求,索性干脆全部叫醒。。。当然,最终只会有一个进程成功地accept(),其它N-1个进程accept()失败。大神们认为这些工作进程都是被“吓醒”的,所以亲切地称之为Thundering Herd(惊群)。
打个比方,一家麦当劳餐厅有4个服务窗口,每个窗口各有一名服务员。当大门口进来一位新客人(相当于有一个新的Socket Client要来connect()本服务器,服务器的操作系统底层收到了这个connect()请求),“欢迎光临!”这时餐厅大门的感应式门铃自动响了,这4名服务员都听到了门铃声,都抬起了头(相当于操作系统唤醒了所有工作进程)并希望将客人招呼到自己所在的服务窗口去(所有工作进程都在执行accept(),希望得到本次connect()连接)。但结果可想而知,客人最终只会走向其中某一个窗口(这次connect()请求最终被某个进程抢先一步accept()成功),而其他3个窗口的服务员只能“失望叹息”(这一声无奈的叹息就相当于accept()返回EAGAIN错误),然后又埋头继续忙自己的事去。
在这个过程中,每有一个connect()就要唤醒所有进程去accept(),并且注定会有N-1个进程失败,白白付出本次开销。
《UNIX网络编程卷1》提到“当某一时刻只有一个连接过来时,N个睡眠进程会被同时叫醒,但只有一个进程可获得连接。如果每次唤醒的进程数目太多,会影响一部分系统性能”。也就是说,“惊群”效应并不只是发生在使用了epoll的程序上,但凡fork()多进程去阻塞式地accept()同一个Socket fd描述符,都会存在这个问题,并且带来资源浪费。那么,有木有好的解决方法呢?
三、探索解决方案:
在网上读了N多帖子,阅读了多款优秀开源程序的源代码,再结合自己的实验,总结如下:
四、smtpproxy采用的解决方案:
综合lighttpd、nginx等软件处理惊群效应时的解决方案以及smtpproxy的实际需求,最终选择了参考lighttpd的Watcher/Workers模型来重构多进程版epoll smtpproxy程序。核心流程则变成:
由于服务器的内核版本仍低于4.5(无法利用到EPOLL_EXCLUSIVE特性),采用这套方案在第3步依旧会出现惊群效应,主动捕获错误并无视,这一点与lighttpd一样。
五、小结:
在如今的web、邮箱、游戏等领域的Linux服务器端程序的开发中,epoll大行其道,也确实是一个很优秀的东西,但一旦多进程就容易碰到各种问题,惊群只是其中之一。
对于本次重构smtpproxy来说,多进程epoll的新版本程序在线上的表现更加优秀,借着这次分享希望能给有需要的童鞋带来一些启发,如有错漏恳请指正。
六、参考文章:
http://www.cnblogs.com/Anker/p/3265058.html
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=946261
https://www.zhihu.com/question/20122137
http://www.iteye.com/topic/382107
http://static.usenix.org/event/usenix2000/freenix/full_papers/molloy/molloy.pdf
https://www.zhihu.com/question/24169490
http://www.man7.org/linux/man-pages/man2/epoll_ctl.2.html
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者陈俊平授权发布。