说说C10K的问题及解决办法

 2024-03-10 05:04:53  阅读 0

1.C10K问题

大家都知道,互联网的基础是网络通信。 早期的互联网可以说是一个小群体的集合。 互联网还不够普及,用户也不多。 一台服务器同时有100个用户在线,在当时大概已经算是一个大规模的应用了。 所以C10K没有问题。 互联网的爆发期应该是在www网站、浏览器和雅虎出现之后。 最早的互联网被称为Web1.0。 互联网的大部分使用场景是下载HTML页面,用户在浏览器中查看网页上的信息。 这期间也没有C10K的问题。

通信中发生异常_epoll大量通信出问题_通信故障原因

Web2.0时代到来后,情况就不一样了。 一方面,渗透率大幅提升,用户群体呈指数级增长。 另一方面,互联网不再是单纯的浏览万维网,而是逐渐开始交互,应用的逻辑也变得更加复杂,从简单的表单提交到即时通讯、在线实时交互。 C10K 的问题变得显而易见。 每个用户必须与服务器保持TCP连接以进行实时数据交互。 这样的网站可能同时有超过1亿个并发TCP连接。

腾讯QQ也存在C10K的问题,但是他们使用UDP这种原始的包交换协议来实现,绕过了这个问题。 当然这个过程肯定会很痛苦。 如果当时有epoll技术的话,他们肯定会使用TCP。 后来的手机QQ、微信都采用了TCP协议。

这个时候问题就出现了。 最初的服务器基于进程/线程模型。 当新的 TCP 连接到达时,需要分配一个进程(或线程)。 进程是操作系统最昂贵的资源,一台机器不可能创建很多进程。 如果C10K需要创建10000个进程,操作系统就无法承受。 如果采用分布式系统,需要10万台服务器才能维持1亿用户在线。 成本是巨大的。 只有雅虎有财力购买这么多服务器。 这就是C10K问题的本质。

其实当时也有异步模式,比如/poll模式。 这些技术有一定的缺点。 例如最大限制不能超过1024,poll没有限制。 但每次接收数据时,都需要遍历每个连接,看看是哪个连接有数据请求。

2. 解决方案

解决这个问题主要有两个思路:一是为每个连接处理分配一个独立的进程/线程;二是为每个连接处理分配一个独立的进程/线程。 另一种是使用同一个进程/线程同时处理多个连接。

2.1 每个进程/线程处理一个连接

这个想法是最直接的。 但由于应用进程/线程会占用相当大的系统资源,且多进程/线程的管理会给系统带来压力,因此该方案不具备良好的扩展性。

因此,当服务器资源不够丰富时,这种想法并不可行; 即使资源足够丰富,效率也不够高。

问题:资源占用过多,扩展性差。

2.2 每个进程/线程同时处理多个连接(IO复用)

1)传统思维最简单的方法是循环中逐个处理每个连接,每个连接一一对应。 当都有数据的情况下这个方法是可行的。 但是当应用程序还没有准备好读取某个文件数据时,整个应用程序就会阻塞在这里等待文件句柄。 即使其他文件句柄准备就绪,它也无法继续处理。

思路:直接在循环中处理多个连接。

问题:任何文件句柄失败都会阻塞整个应用程序。

2)解决上面的阻塞问题,思路很简单。 如果我在读取文件句柄之前检查一下文件句柄的状态,如果准备好了就处理它,如果没有准备好就不处理,那么这个问题就解决了。 出色地? 于是就有了一个计划。 使用一个结构体告诉内核同时监视多个文件句柄。 当文件句柄之一的状态更改为指定更改(例如,句柄从不可用更改为可用)或超时时,调用将返回。 然后应用程序可以使用它来一一查看哪个文件句柄已更改其状态。 这样,小规模的连接问题不大,但是当连接很多(文件句柄很多)时,逐一检查状态就会很慢。 因此,经常有托管句柄 caps()。 同时,在使用中,由于只有一个字段来记录关注点和事件,因此每次调用前都必须重新初始化该结构。

(int nfds, *, *, *, *);

思路:当有连接请求到来时,再次检查并处理。

问题:句柄上限+重复初始化+一一检查所有文件句柄的状态,效率不高。

3)主要解决前两个问题:将需要关注的事件通过数组传递给内核,消除文件句柄的上限,并使用不同的字段分别标记关注事件和发生事件,避免重复初始化。

思路:设计一种新的数据结构,提供使用效率。

问题:一一检查所有文件句柄的状态效率不高。

4)由于epoll在一一检查所有文件句柄的状态时效率不高,很自然,如果调用返回并且只向应用程序提供状态发生改变的文件句柄(很可能是数据就绪),那么epoll的效率就会降低检查不会高很多。 什么? epoll采用了这种设计,适合大规模应用场景。 实验表明,当文件句柄数超过10个时,epoll的性能会优于poll; 当文件句柄数达到10K时,epoll已经超过了poll两个数量级。

思路:只返回状态发生变化的文件句柄。

问题:对特定平台(Linux)的依赖性。

由于Linux是互联网公司最常用的操作系统,Epoll已经成为C10K、高并发、高性能、异步非阻塞等技术的代名词。 推出了,Linux推出了epoll,推出了IOCP,推出了/dev/poll。 这些操作系统提供的功能都是为了解决C10K问题。 epoll技术的编程模型是异步非阻塞回调,也可以称为事件驱动、事件轮询(event round-robin)。 Nginx、node.js都是Epoll时代的产物。

5)由于epoll、IOCP的各个接口都有各自的特点,程序移植非常困难,所以需要对这些接口进行封装,使其易于使用和移植,而库就是其中之一。 跨平台,它封装了底层平台的调用,提供统一的API,但底层会自动选择不同平台上合适的调用。 据官网介绍,该库提供了以下功能:当文件描述符的特定事件(如可读、可写或错误)发生时,或者定时事件发生时,会自动执行用户指定的回调函数来处理事件。 目前支持以下接口/dev/poll、事件端口、poll 和 epoll。 内部事件机制完全基于所使用的接口。 因此,它非常容易移植,并且也使其可扩展性非常容易。 目前,它已在以下操作系统上编译:Linux、BSD、Mac OS X 和 . 使用该库进行开发非常简单,并且可以轻松地跨各种 UNIX 平台移植。 使用该库的简单程序如下:

通信中发生异常_通信故障原因_epoll大量通信出问题

3 协程()

随着技术的演进,epoll已经能够更好的处理C10K的问题,但是如果需要进一步扩展,比如支持10M并发连接,原有的技术就无能为力了。

那么,新的瓶颈在哪里?

从之前的演化过程我们可以看出,其基本思想就是有效地去除阻塞,让CPU能够做核心任务。 所以,千万并发实现的秘诀:内核不是解决方案,而是问题!

这意味着:

不要让内核承担所有繁重的工作。 将包处理、内存管理、处理器调度等任务从内核转移到应用程序以高效地完成。 让Linux只处理控制层,将数据层完全留给应用程序。

当连接很多的时候,首先需要很多进程/线程来做事情。 同时,系统中大量的应用进程/线程可能处于就绪状态,需要系统不断地快速切换,而我们知道系统上下文切换是有代价的。 虽然Linux系统的调度算法已经设计得非常高效,但是对于10M这样的大规模场景来说还是显得不足。

所以我们面临两个瓶颈。 一是进程/线程作为处理单元仍然太重; 二是系统调度成本过高。

自然地,我们会认为如果有一个更轻量级的进程/线程作为处理单元就完美了,并且它们的调度可以很快完成(最好没有锁)。

这样的技术现在在一些语言中有一些实现,它们就是(协程),或者说合作例程。 具体来说,Lua语言中的(协程)模型和Go语言中的(Go例程)模型都是类似的概念。 事实上,类似的模型可以用很多语言(甚至C)来实现。

在实现上,他们都尝试使用一小组线程来实现多个任务。 一旦一个任务被阻塞,同一个线程可以用来继续运行其他任务,以避免大量的上下文切换。 每个协程独占的系统资源往往只有栈部分。 而且,各种协程之间的切换往往是由用户通过代码显式指定的(类似于各种其他进程)。 它不需要内核的参与,可以轻松地异步实现。

该技术本质上是一种异步非阻塞技术。 它包装了事件回调,使程序员无法看到内部的事件循环。 这就像为程序员编写阻塞代码一样简单。 比如调用->recv()等待接收数据时,像阻塞代码一样写。 事实上,底层库在执行recv时会偷偷保存一个状态,比如代码行数、局部变量的值等。 然后它跳回中心。 当真正的数据到来时,它会取出刚刚保存的代码行数和局部变量值,并重新开始执行。

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

3.1 同步阻塞

不知道大家看完协程是否能感受到。 其实协程和同步阻塞是一样的。 答案是肯定的。 因此,协程也被称为用户模式线程/用户模式线程。 区别在于进程/线程是由操作系统调度的,而协程是使用Epoll自行调度的。

协程的优点是它们的开销比系统线程少。 缺点是如果其中一个协程计算密集,其他协程就不会运行。 操作系统进程的缺点是成本昂贵。 优点是无论代码怎么写,所有进程都可以并发运行。

解决了协程密集计算的问题。 它基于自主开发的VM,不执行机器代码。 即使在密集计算场景下,如果VM发现某个协程的执行时间过长,仍然可以进行中止切换。 由于直接执行机器码,所以无法解决这个问题。 因此,需要用户在计算密集型代码中自行屈服。

事实上,同步阻塞程序的性能还不错,非常高效,而且不浪费资源。 当进程被阻塞时,操作系统会将其挂起,并且不会分配CPU。 直到数据到达才分配CPU。 运行多个进程的副作用太大,因为进程之间的切换有开销。 所以如果一个服务器程序只有1000个左右的并发连接,那么同步阻塞模式是最好的。

3.2 异步回调和协程哪个性能更好?

虽然协程是在用户态调度的,但实际上还是需要调度的。 既然调度了,就会有上下文切换。 因此,虽然协程的性能优于操作系统进程,但仍然存在额外的消耗。 异步回调没有切换开销,相当于顺序执行代码。 因此,异步回调程序的性能优于协程模型。

专栏作者简介 ( )

陶邦仁:做一个更注重实事的有用人

通信中发生异常_通信故障原因_epoll大量通信出问题

提示支持作者写出更多好文章,谢谢!

【今日微信公众号推荐↓】

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码