高并发网络服务的思考

最近一直在学习Twisted, Python的异步网络开发框架。也看了一些与其相关的资料,把所了解到的一些内容和想法记录一下。

通常我们所熟悉的网络服务开发,不论是天天在使用的Java Servlet也好,还是Python下的各种以WSGI为基础的Web Framework也好,都是采用多线程的方式处理并发请求。在主线程中监听,建立Socket后交给线程来处理,主线程继续等待请求。

这种模式在目前的系统中大量被应用。曾经也考虑过,每一个线程的代价是多少?这些线程真的是并行执行的吗?当线程访问IO,比如访问磁盘,数据库时,线程的等待的代价是多少?对于主要业务是CRUD的Web系统,线程接受一个请求后,以下各部分占总处理时间的比例是多少?

  • 从Request中读取数据
  • 处理请求
  • 访问数据库
  • 处理数据
  • 写回Response

每台服务器最多能同时启动多少线程来应对并发?如今很多网站要面对每秒几K,几十或者几百K的并发访问(更不要提DDOS的攻击),需要多少硬件来处理这样的访问量?

前些天看到一个Slide,有一组数字来对比不同IO的代价(数字来自于:Node .js by Francisco Treacy):

L1 Cache

3 cycles

L2 Cache

14 cycles

RAM

250 cycles

Disk

41,000,000 cycles

Network

240,000,000 cycles

这些数字看起来很恐怖,磁盘IO是内存的16万倍。读写数据库等常见的网络IO是内存读写的将近100万倍。

虽然没有实际的数据说明CRUD系统各个部分的消耗时间占比,但是总上面这些数据可以很容易看出,1,3,5步的时间肯定比2,4步多几个数量级。那么也可以说,一个线程被从Pool中取出到再回到Pool中所经历的时间,大部分在IO阻塞中等待。

虽然不能确定线程在被IO阻塞的时候需要占用多少系统资源,但是系统毕竟不可能同时运行太多的线程,也就是说,就算大量的线程在等待,系统也无法在负载更多的并发请求。

相对于这样的一种模式,现在常见的另一种模式,也就是异步网络开发模式。在异步模式下,所有请求运行在一个进程/线程中。系统最核心的是一个基于事件的无限循环,每一次循环,系统会等待所有的IO,任何一个或者多个IO准备好了读或者写,等待结束,系统开始依次处理每一个IO请求,然后系统进入下一轮等待。这种模式叫Reactor Pattern。这样的方式要求每一个处理IO请求的处理单元,必须尽量的充分利用CPU资源,如果遇到任何高消耗的IO操作,都要交由系统的核心,重新进入循环调度,而不能等待。这种的调度方式也叫做cooperative multitask,这在早期的计算机系统中大量使用,Mac OS 9版本也采用类似的调度核心。

(Borrowed from Dave Peticolas’ An Introduction to Asynchronous Programming and Twisted Part 2)

对比同步和异步的开发模式,在同步模式下,开发人员可以随意使用CPU资源,开发人员假设外部系统能够完美的处理好多线程的调度,外部系统全权包办CPU资源的调配,应用可以随意贪婪的使用CPU资源。异步模式则将系统资源的使用权很大程度上交由开发人员来决定,系统信任每一个模块/处理会自觉得使用仅必须的CPU时间,而且会自觉的将CPU资源让出给其他的模块/方法使用。这听上去好像很原始,开发人员需要用很大的精力去考虑非业务的问题。当需要面对的是一个高并发访问的系统,访问的稳定性和快速响应,应该比复杂花哨的功能更有价值吧。

(PS. 这两种模式的对比感觉有点像当今的社会现象,看看日本震后群众的表现就知道了)

从另外一个角度来对比,为了能够处理大量的并发,同步模式使用了Queue来缓冲超过处理能力的请求,即使所有的线程都在等待IO(如数据库响应),这些在Queue中的请求也无法得到处理。而异步模式下,即使已经接受了大量的请求,只要这些请求都在等待,再来一个新请求的时,也能够立即开始处理新的请求。从整体上来看,异步模式的系统响应应该更快,即使不总是能够最快的返回所有请求的数据,但是也能够更快速的开始反馈,当然这需要有一个优良设计的系统来支撑,不然异步模式就成为了一次只能响应一个请求的系统。

Load Balancer这样的应用,本身的处理逻辑很简单,只需要按照某种规则将外部的请求转发到内部的某一台Server上,然后将Server的response返回给用户。但是这样的应用是直接面对并发的,后台应用可以使用大量的节点的cluster,但是Load Balancer总是要单独面对。同时能够处理大量的IO正是异步系统的特长。采用异步的方式,Balancer可以同时打开大量的Socket(对外及对内的),而不需要消耗线程资源来等待。Nginx正式采用了异步的模式,也就是基于Event的模式来达到极高的负载能力和响应速度的。

总结一下,异步模式适合于有大量IO操作,需要花大量时间在IO等待的应用,而不是适合于需要大量CPU计算的应用。我们经常开发的基于CRUD的系统正是这样一种有着大量IO等待的应用。

This entry was posted in 计算机与 Internet. Bookmark the permalink.

1 Response to 高并发网络服务的思考

  1. Elaine says:

    看得我有点累啊。
    先表扬一下,在我写了4,5篇之后,你终于动笔写了一篇了,值得表扬。继续努力。
    不过我要提点意见,语言不够生动,文字应该可以更灵动一下,以达到吸引人读下去的目地。并享受阅读过程。

Leave a comment