2012年4月22日星期日

仲宣楼头,千丈白发,怅盘桓而不能去

 读杜诗《短歌行赠王朗司直》有感

2012年4月7日星期六

大量短连接的Server中的惊群问题

最近继续完善自己的多进程非阻塞网络服务框架arch,对于所谓的惊群(Thundering herd)的问题,查看了在这个问题上nginx源码以及其它一些非开源代码,公司内部代码中的处理方式,有以下的思考:

惊群问题(Thundering herd problem),wiki上的解释简单说是“大量处理进程/CPU阻塞在一个调用上,由于某个触发条件导致都被唤醒,但只有一个进程能继续处理,其它均失败”。惊群现象会浪费较多的CPU。这种现象可以出现在很多场景中,以下讨论的具体场景为处理大量短连接的Server中的惊群。

这种场景以Web Server为典型,一般有多个进程/线程同时监听80端口(poll非阻塞IO方式),则当有一个客户端连接上来时,所有的等待者均被唤醒,继而都调用accept处理,但只有一个能成功,其它均失败。

目前较多的实现是基本不考虑惊群问题,如检查的第一个公司内部实现代码,lighthttpd。Redis因为是单进程单线程的实现,故没有考虑此问题。

Nginx是用进程锁的方式解决该问题,即确保在某一个时刻,只有一个进程监听端口处理接收的连接;后续通过比较复杂的手段在多个进程间切换监听工作达到负载均衡的效果,仍然用到进程锁。

 

Nginx的实现对于我来说过复杂了。我在arch中用了一个较为简单方式解决这个问题。

arch是一个多进程的框架实现,故这里先讨论多进程模型下的一种较简单的解决方法:

1. 一个主控进程(Manager)监听指定端口,将FD加入到非阻塞IO集合中,用类poll方式监听(select/poll/epoll)

2. 启动N(N=CPU个数)个工作IO进程(Worker),建立Worker和Manager之间的通信连接(Linux下只能用UnixDomainSocket, 某些Unix可用Stream管道)

3. 以上是进程初始化时的工作,当一个连接发生时,Manager接受此链接,马上将该连接的文件描述符发送到一个Worker进程(同时关闭文件描述符),马上返回到自己的等待状态等待处理下一个连接;(这里的选择Worker进程即是复杂均衡的判断逻辑,一般来说轮询选择就可以达到较好的平衡状态)

4. Worker进程接收到文件描述符后,加入到自己的非阻塞IO集合,读取数据处理业务逻辑

同Nginx的实现一样,arch也避免了惊群现象,不同之处在于arch用到了进程间文件描述符传送,避免了复杂的锁实现;而Nginx用了进程锁,避免了文件描述符传送的系统调用;后续会比较一下两者性能区别。

 

多线程模型下较为简单,只需要采用类似Nginx的锁方式,结合arch的manager+worker模型:

1. 启动Manager线程,监听定端口,将其加入到非阻塞IO集合中,用类poll方式监听(select/poll/epoll)

2. 启动N(N=CPU个数)个工作IO线程(Worker)

3. 当一个连接发生时,Manager接受此链接,马上将该连接的文件描述符交给一个Worker线程马上返回到自己的等待状态处理下一个连接;(这里的选择Worker进程即是复杂均衡的判断逻辑,一般来说轮询选择就可以达到较好的平衡状态)

4. Worker线程接收到文件描述符后,加入到自己的非阻塞IO集合,读取数据处理业务逻辑。

不过,对于编写C/C++的Server程序来说,多线程应当是尽量避免的。这倒是题外话了。