c10k问题

当网络服务在处理数以万计的客户端连接时,往往出现效率低下甚至完全瘫痪,这被称为C10K问题。随着互联网的迅速发展,越来越多的网络服务开始面临C10K问题,作为大型网站的开发人员或者服务器端编程人员有必要对C10K问题有一定的了解。这文章是分析如果编写一个服务器程序来支持上万的客户端连接的。其关注的重点是io效率,目前大规模网络程序的关键瓶颈。

作者认为需要权衡利弊的要点有以下

1.单线/进程处理多个I/O的做法: 其可选的方案有
    a. 不这样做。也就是对单线程使用阻塞同步的IO(意为依靠多线/进程来处理多个IO)。
    b. 使用非阻塞IO。即以非阻塞方式启动IO,并等待IO就绪通知(select / poll等,通常对网络IO有效,但不能提高磁盘IO的效率)
    c. 使用异步IO。调用 aio_write启动IO,并等待IO的完成通知。

2.如果管理client连接
    a. 一个进程处理一个client (经典unix方式)
    b. 一个OS-level线程处理多个client,对应每个client使用一个user-level线程(即线程库,虚拟机提供的线程,协同等方式)
    c. 一个OS-level线程处理一个client (java的内置线程等)
    d. 一个OS-level线程处理一个活跃的client

3. 是否使用标准OS服务,还是把依靠具体OS内核

以上的选择互相组合,产生一些常见解决方案.

1. 单线程处理多client,使用非阻塞IO 和水平触发的就绪通知

**水平触发(level-trigger)与边界触发(edge-trigger)对应,简要说水平触发意为根据状态
也判断是否触发某事件, 而边界触发根据变化来判断是否触发某事件。

也就是传统的io多路复用,这是目前大部分网络程序的解决方式,它可以方便的在一个线程里管理多个
io。不需要考虑多线/程等问题, 代码的逻辑不需要加入额外的复杂性。

但这里所指的非阻塞,是指在某个fd未就绪时的read或write操作, 程序不等待它的就绪而已。
这边存在上面说对于磁盘IO并没有提高效率的问题。在大规模地对磁盘读写操作时, read 或write还是会导致整个程序长时间阻塞。

如果要避免这种情况,必须引入异步IO。 对于部分缺乏AIO的系统,就只能建立子线/进程来完成该操作。
另外一种对磁盘IO的优化方式是使用 内存映像IO。

这种方式的关键是判断一组非阻塞的socket何时处于就绪状态。
实现它们的方法有 select, poll, dev/poll 等。

2. 单线程处理多client,使用非阻塞IO 和就绪变化通知

意为使用边界触发的就绪通知。其实这种方案只是1的加速方式, 即只在fd状况发生变化时
才去检查fd是否变为就绪。这种实现必须去处理假事件,因为有些实现方式是fd接到任何包的时候都假定其变为就绪了。

实现它们的有 kqueue, epoll(指定ET模式)等。

一些通用的库如libevent ACE等,把这些底层不同实现封装起来,并提供不同IO策略的选择。 按照
libevent作者Niels的测试数据 kqueue在所有之中性能最高。

 

3. 多个线程处理多个client, 使用异步IO

这是对于网络IO和磁盘IO都很高效的方式。通常当异步IO发起后, 它使用边界触发的方式来提供IO的完成通知。
然后把完成的通知放到消息队列里,等待程序的后续相应。

但这需要os提供AIO的支持,并且需要以异步的方式来设计程序,有一定复杂性。

4. 多个线程处理client

这是最简单方式,就是让但进/线程 的read 和 write 直接阻塞。
这个方法的最大缺点是, 需要支付每个进/线程的栈空间,并引入额外的上下文切换的开销。
如果需要上万个client, 建立上万的进/线程目前是不太可能的。

5. 把服务器整合到os内核里
    部分系统有这样的尝试,例如Liunx下的 khttpd 就是把web服务器整合到
系统内核里。 但这对我们的游戏参考价值不大。

原文地址:
http://www.kegel.com/c10k.html

你可能感兴趣的:(c,IO,网络,服务器,web服务,磁盘)