空开上的c10是什么意思,c100空开是多少A的
1 C10K问题
众所周知,互联网的基础是网络传播,早期的互联网可以说是小群体的集合。互联网不够普及,用户也不多。一个100个用户同时在线的服务器,估计是当时的大型应用。所以不存在C10K的问题。互联网的爆发应该是在www网站、浏览器、雅虎出现之后。最早的互联网叫Web1.0,互联网的大部分使用场景是下载一个Html页面,用户在浏览器中查看网页上的信息。这个时期不存在C10K问题。
Web2.0时代到来后,就不一样了。一方面,普及率大大提高,用户群体呈几何级数增长。另一方面,互联网不再是简单的浏览万维网网页,而是逐渐开始交互,应用程序的逻辑变得更加复杂,从简单的表单提交到即时通讯和在线实时交互。C10K的问题就体现出来了。每个用户都必须与服务器保持TCP连接,以便进行实时数据交互。脸书等网站同时并发TCP连接的数量可能超过1亿。
腾讯也有C10K的问题,但是他们用UDP这种原始的分组交换协议来实现,从而绕过了这个问题。当然,过程肯定是痛苦的。如果当时有epoll技术,他们肯定会用TCP。后来手机QQ和微信都采用了TCP协议。
这时,问题就出现了。最初的服务器都是基于进程/线程模型的。当一个新的TCP连接到达时,需要分配一个进程(或线程)。而且进程是操作系统最昂贵的资源,一台机器不可能创建很多进程。如果是C10K,要创建10000个进程,那么操作系统承受不起。如果采用分布式系统,要保持1亿用户在线需要10万台服务器,成本很大。只有脸书、谷歌和雅虎买得起这么多服务器。这就是C10K问题的本质。
其实当时也有异步模式,比如选择/轮询模式。这些技术都有一定的缺点,比如selelct最大不能超过1024。Poll没有限制,但是每次接收到数据时,都需要遍历每一个连接,看哪一个有数据请求。
2个解决方案
解决这个问题主要有两种思路:一种是给每个连接进程分配一个独立的进程/线程;另一个想法是使用同一个进程/线程同时处理几个连接。
2.1每个进程/线程处理一个连接。
这个想法是最直接的。但是应用进程/线程会占用相当多的系统资源,多个进程/线程的管理会给系统带来压力,所以这种方案不具备良好的可扩展性。
所以这种想法在服务器资源不够丰富的情况下是不可行的;即使资源足够丰富,效率也不够高。
问题:资源占用过多,扩展性差。
2.2每个进程/线程同时处理多个连接(IO多路复用)
传统思维
最简单的方法是逐个循环处理每个连接,每个连接对应一个套接字。当所有套接字都有数据时,这种方法是可行的。
但是,当应用程序没有准备好读取一个套接字的文件数据时,整个应用程序将阻塞在这里等待文件句柄,即使其他文件句柄准备好了,也无法进一步处理。
思路:直接回收多个连接。
问题:任何文件句柄的失败都会阻塞整个应用程序。
挑选
要解决上面的阻塞问题,思路很简单。如果我在读取文件句柄之前检查它的状态,它准备好了我就处理,没准备好我就不处理,这样不就解决问题了吗?
所以有了选择方案。fd_set结构用于告诉内核同时监控多个文件句柄。当其中一个文件句柄的状态发生变化(例如,一个句柄从不可用变为可用)或超时时,调用返回。之后,应用程序可以使用FD_ISSET逐个检查哪个文件句柄的状态发生了变化。
这样小范围的连接问题不大,但是当连接比较多的时候(大量的文件句柄),逐个检查状态就比较慢了。因此,select总是有一个句柄上限(FD_SETSIZE)。同时,在使用中,由于记录关注点和事件的字段只有一个,所以在每次调用前都要重新初始化fd_set结构。
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct time val * time out);
想法:当连接请求到达时,检查并处理它。
问题:句柄上限的重复初始化对于逐个检查所有文件句柄的状态是低效的。
投票
Poll主要解决select的前两个问题:通过一个pollfd数组将需要关注的事件消除文件的句柄上限传递给内核,用不同的字段分别标记关注事件和发生事件,避免重复初始化。
int poll(struct pollfd *fds,nfds_t nfds,int time out);
想法:设计一个新的数据结构来提供效率。
问题:逐个检查所有文件句柄的状态是低效的。
使用
由于逐个检查所有文件句柄的状态是低效的,自然地,如果应用程序仅被提供了在调用返回时状态改变(可能是数据就绪)的文件句柄,检查的效率将会高得多。
Epoll采用这种设计,适合大规模应用场景。
实验表明,当文件句柄数量超过10时,epoll的性能将优于select和poll。当文件句柄的数量达到10K时,epoll已经超过了select和poll两个数量级。
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int time out);
想法:只返回状态改变的文件句柄。
问题:依赖于特定的平台(Linux)。
由于Linux是互联网企业使用率最高的操作系统,Epoll成为了C10K杀手、高并发、高性能、异步、无阻塞技术的代名词。BSD推出了kqueue,Linux推出了epoll,Windows推出了IOCP,Solaris推出了/dev/poll。这些操作系统提供的功能就是为了解决C10K问题。epoll技术的编程模型是异步非阻塞回调,也可称为Reactor、event-driven和EventLoop。Nginx,libevent,Node.js这些都是Epoll时代的产物。
关于select、poll和epoll的具体原理的详细说明,请参考:《聊聊IO多路复用之select、poll、epoll详解》。
机制
由于epoll、kqueue、IOCP的每个接口都有自己的特点,所以程序的移植非常困难,所以需要对这些接口进行封装,使其易于使用和移植,libevent库就是其中之一。跨平台,封装底层平台的调用并提供统一的API,但底层平台自动选择不同平台上合适的调用。
根据libevent官网介绍,libevent库提供了以下功能:当一个文件描述符的特定事件(如可读、可写或错误)发生,或者定时事件发生时,libevent会自动执行用户指定的回调函数来处理事件。目前,libevent已经支持以下接口/dev/poll、kqueue、eventports、select、poll和epoll。Libevent的内部事件机制完全基于所使用的接口。所以libevent非常容易移植,这也使得它的可扩展性非常容易。目前,libevent已经编译在以下操作系统中:Linux、BSD、Mac OS X、Solaris和Windows。
使用libevent库进行开发非常简单,在各种unix平台上移植也很容易。下面是一个使用libevent库的简单程序:
输入图片描述。
3协同程序
随着技术的演进,epoll已经能够很好的处理C10K问题,但是如果需要进一步扩展,比如支持10M规模的并发连接,原有的技术就无能为力了。
那么,新的瓶颈在哪里?
从之前的演进过程中,我们可以看到,根本的思路是高效地解除对CPU的阻塞,使其能够完成核心任务。所以,数千万并发实现的秘密:内核不是解决方案,而是问题!
这意味着:
不要让内核执行所有繁重的任务。将数据包处理、内存管理、处理器调度等任务从内核转移到应用程序,高效完成。让Linux只处理控制层,把数据层留给应用。
当有很多连接的时候,首先需要很多进程/线程来做事情。同时,系统中大量的应用进程/线程可能处于就绪状态,这就需要系统不断的快速切换,我们知道系统上下文的切换是有代价的。虽然Linux系统的调度算法已经被设计得非常高效,但是对于10M这样的大规模场景来说,还是显得力不从心。
所以我们面临两个瓶颈,一个是进程/线程作为处理单元还是太重;另一个是系统调度的成本太高。
很自然,我们会认为,如果有一个更轻的进程/线程作为处理单元,并且它们的调度可以快速完成(最好没有锁),那就完美了。
这些技术已经在一些语言中实现了,它们是协同例程,或协作例程。具体来说,Python,Lua语言中的协程模型,Go语言中的Go例程模型是类似的概念。其实很多语言(甚至C语言)都可以实现类似的模型。
在实践中,它们都试图用少量的线程完成多个任务。一旦一个任务被阻塞,同一个线程可以继续运行其他任务,以避免大量的上下文切换。每个进程独占的系统资源往往只是堆栈部分。而且各种协程之间的切换往往是用户通过代码明确指定的(类似于各种回调),所以不需要内核参与就可以方便地实现异步。
这种技术本质上也是一种异步无阻塞技术。它包装了事件回调,这样程序员就看不到里面的事件循环了。程序员就像写阻塞代码一样简单。比如调用client-recv()等待接收数据的时候,像写阻塞代码一样写。实际上,底层库在执行recv时会悄悄地保存一个状态,比如代码行数和局部变量的值。然后跳回事件循环。当真正的数据到达时,它取出刚刚保存的代码行数和局部变量值,再次开始执行。
这是协同学的精髓。协同处理是异步非阻塞的另一种表现形式。Golang,Erlang,Lua协同学都是模型。
3.1同步闭锁
不知道大家看了协同过程后能不能感受到。其实协同过程和同步阻断是一样的。答案是肯定的。所以协程也叫用户态进度/用户态线程。不同的是,进程/线程被操作系统调度为EventLoop,而进程/线程是由Epoll自己调度的。
协程的优点是比系统线程花费少,但缺点是如果一个协程有密集的计算,其他协程就不会运行。操作系统进程的缺点是开销大,优点是不管代码怎么写,所有进程都可以并发运行。
Erlang解决了协作密集型计算的问题。基于自研VM,不执行机器码。即使存在密集计算的场景,VM也可以中止和切换,即使它发现协程执行时间太长。Golang无法解决这个问题,因为它直接执行机器码。所以Golang要求用户在密集计算的代码中屈服。
其实同步阻塞程序的性能还不错,非常高效,不会浪费资源。当一个进程被阻塞时,操作系统会挂起它,不会分配CPU。在数据到达之前,不会分配CPU。进程太多副作用太多,因为进程太多,互相切换有开销。因此,如果一个服务器程序只有大约1000个并发连接,那么同步阻塞模式是最好的。
3.2异步回调和协同哪个性能更好?
虽然协调过程是用户状态调度,但它实际上需要被调度,因为在调度中会有上下文切换。因此,尽管协同进程的性能优于操作系统进程,但总会有额外的消耗。但是异步回调没有切换开销,相当于顺序代码执行。因此,异步回调程序的性能优于协同模型。
转子:https://www.cnblogs.com/jjzd/p/6540205.html
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。