C10K问题

开宗明义,epoll以及BSD的kqueue就是推出来解决 C10K的。Linux2.6 2003年左右。

 

可以看这篇文章

http://www.oschina.net/translate/the-secret-to-10-million-concurrent-connections-the-kernel

另外我之前的文章

http://www.cnblogs.com/charlesblc/p/6242479.html

也讲到了百万级别连接的要点 epoll的一些内部实现

 

还有这一篇:

http://rango.swoole.com/archives/381

Epoll就是为了解决C10K问题而生。

Nginx,libevent,node.js这些就是Epoll时代的产物。

 

当程序员还沉浸在解决C10K问题带来的成就感时,一个新的问题被抛出了。异步嵌套回调太TM难写了。

于是一个新的技术被提出来了,那就是协程(coroutine)。这个技术本质上也是异步非阻塞技术,它是将事件回调进行了包装,让程序员看不到里面的事件循环。

程序员就像写阻塞代码一样简单。比如调用 client->recv() 等待接收数据时,就像阻塞代码一样写。实际上是底层库在执行recv时悄悄保存了一个状态,比如代码行数,局部变量的值。然后就跳回到EventLoop中了。什么时候真的数据到来时,它再把刚才保存的代码行数,局部变量值取出来,又开始继续执行。

 

这就是协程的本质。协程是异步非阻塞的另外一种展现形式。Golang,Erlang,Lua协程都是这个模型。

注:我理解,就是把异步交给语言去处理;暴露的接口是同步形式的。

 

实际上同步阻塞程序的性能并不差,它的效率很高,不会浪费资源。当进程发生阻塞后,操作系统会将它挂起,不会分配CPU。直到数据到达才会分配CPU。多进程只是开多了之后副作用太大,因为进程多了互相切换有开销。所以如果一个服务器程序只有1000左右的并发连接,同步阻塞模式是最好的。

 

异步回调是没有切换开销的,它等同于顺序执行代码。所以异步回调程序的性能是要优于协程模型的。

这里是指Nginx这种多进程异步非阻塞程序。Node.js/Redis此类程序如果不开多个进程,由于无法利用多核计算优势,所以性能并不好。


再看第三篇

C10K的问题——过去十年 

十年前,工程师在处理C10K可扩展性问题时,都尽可能的避免服务器处理超过10,000个的并发连接。通过修正操作系统内核以及用事件驱动型服务器(如Nginx和Node)替代线程式的服务器(如Apache)这个问题已经解决。从Apache转移到可扩展的服务器上,人们用了十年的时间。在过去的几年中,(我们看到)可扩展服务器的采用率在大幅增长。

Apache的问题

  • Apache的问题是,(并发)连接数越多它的性能会越低下。
  • 关键问题:(服务器的)性能和可扩展性并不是一码事。它们指的不是同一件事情。当人们谈论规模的时候往往也会谈起性能的事情,但是规模和性能是不可同日而语的。比如Apache。
  • 在仅持续几秒的短时连接时,比如快速事务处理,如果每秒要处理1,000个事务,那么大约有1,000个并发连接到服务器。
  • 如果事务增加到10秒,要保持每秒处理1,000个事务就必须要开启10K(10,000个)的并发连接。这时Apache的性能就会陡降,即使抛开DDos攻击。仅仅是大量的下载就会使Apache宕掉。
  • 如果每秒需要处理的并发请求从5,000增加到10,000,你会怎么做?假使你把升级硬件把处理器速度提升为原来的两倍。会是什么情况?你得到了两倍的性能,但是却没有得到两倍的处理规模。处理事务的规模或许仅仅提高到了每秒6,000个(即每秒6,000个并发请求)。继续提高处理器速度,还是无济于事。甚至当性能提升到16倍时,并发连接数还不能达到10,000个。由此,性能和规模并不是一回事。
  • 问题在于Apache总是创建了一个进程然后又把它关闭了,这并不是可扩展的。
  • 为什么?因为内核采用的O(n^2) 算法导致服务器不能处理10,000个并发连接。
    • 内核中的两个基本问题:
      • 连接数 = 线程数/进程数。当一个包(数据包)来临时,它(内核)会遍历所有的10,000个进程以决定由哪个进程处理这个包。
      • 连接数= 选择数/轮询次数(单线程情况下)。同样的扩展性问题。每个包不得不遍历一遍列表中的socket。
    • 解决方法:修正内核在规定的时间内进行查找
      • 不管有多少线程,线程切换的时间都是恒定的
      • 使用一个新的可扩展的epoll()/IOCompletionPort在规定的时间内做socket查询
  • 由于线程调度依然没有被扩展,因此服务器对sockt大规模的采用epoll,导致需要使用异步编程模式,然而这正是Node和Nginx所采用的方式。这种软件迁移会得到(和原来)不一样的表现(指从apache迁移到ngix等)。即使在一台很慢(配置较低)的服务器上增加连接数性能也不会陡降。介于此,在开启10K并发连接时,一台笔记本电脑(运行ngix)的速度甚至超越了一台16核的服务器(运行Apache)。

 

  • 一个颇具影响的例子,就是在考虑到Apache的线程每个连接模型(is to consider Apache’s thread per connection model)。 这就意味着线程调度器根据到来的数据(on which data arrives)决定调用哪一个(不同的)read()函数(方法)。把线程调度系统当做(数据)包调度系统来使用(我非常喜欢这一点,之前从来没听说过类似的观点)。
  • Nginx宣称,它并不把线程调度当作(数据)包调度来用使用,它自已做(进行)包调度。使用select来查找socket,我们知道数据来了,于是就可以立即读取并处理它,数据也不会堵塞。
  • 经验:让Unix处理网络堆栈,之后的事情就由你自已来处理。

 

posted @ 2017-03-02 15:59  blcblc  阅读(741)  评论(0编辑  收藏  举报