C10K-C10M进阶(高并发的真正理解)

1、一台计算机可以连接的TCP受什么限制?
能打开文件描述符的限制。默认是1024,可以修改。
端口号的限制,65535,因为TCP头部16位的端口号,其中1024以上可以用。如果是客户端,最多可以打开6万多的文件。如果是服务器,还要乘以ip数量。
但是,最终TCP连接数量还是会收到内存、操作系统等限制,不过现在达到数十万应该没问题。

C10K问题
早期网站100人就很多了,后来web1.0时代,人变多了,但也只是简单下载html浏览网页。但是到2.0时代,用户几何增长,而且不仅仅是简单浏览,有复杂的交互。所以网站同一时间的并发TCP连接很可能已经过亿。最初的服务器都是基于进程/线程模型的(典型的就是apache多线程),新到来一个TCP连接,就需要分配1个进程(或者线程)。而进程又是操作系统最昂贵的资源,一台机器无法创建很多进程。如果是C10K就要创建1万个进程,那么单机而言操作系统是无法承受的。

特点: 性能和连接数及机器性能的关系往往是非线性的。它在 2 倍性能新服务器上往往处理不了并发 2倍的吞吐量。这是因为在策略不当时,大量操作的消耗和当前连接数 n 成线性相关。会导致单个任务的资源消耗和当前连接数的关系会是 O(n)
连接数越多,那么单个连接消耗的时间越长。

**本质:**操作系统的问题。同步阻塞I/O模型。创建的进程线程多了,数据拷贝频繁(磁盘文件-page cache-用户-内核socket缓冲区-网卡), 进程/线程上下文切换消耗大, 导致操作系统崩溃,榨干了CPU资源,这就是C10K问题的本质!
(线程切换和线程数量有关,因为要线性查找可以用的空闲线程,所以要epoll来使线程切换和线程数量无关)

解决方案: 非阻塞IO+IO复用机制
给每个连接分配一个进程,不现实的;
也就是等有数据到才分配线程去处理,避免无意义的上下文切换。

select使用fd_set,检查是否发生事件。缺点是fd_set大小限制,拷贝开销和逐个检查以及重复初始化的开销。
poll 主要解决 select 的前两个问题:通过一个 pollfd 数组向内核传递需要关注的事件消除文件句柄上限,同时使用不同字段分别标注关注事件和发生事件,来避免重复初始化。
epoll如果调用返回的时候只给应用提供发生了状态变化(很可能是数据 ready)的文件句柄
由于epoll, kqueue, IOCP每个接口都有自己的特点,程序移植非常困难,于是需要对这些接口进行封装,以让它们易于使用和移植,其中libevent库就是其中之一
(其实这些只是解决了线程切换的问题,数据拷贝的问题没有解决,利用sendfile零拷贝技术可能会效果更好)

总结一下就是不要让CPU处理无关的事情,比如阻塞等待数据到达,比如处理进程的上下文切换,创建销毁,数据拷贝等。
充分利用CPU。

解决C10M问题并非不可能
OS的内核不是解决C10M问题的办法,恰恰相反OS的内核正是导致C10M问题的关键所在。
截至目前,40gpbs、32-cores、256G RAM的X86服务器在Newegg网站上的报价是几千美元。实际上以这样的硬件配置来看,它完全可以处理1000万个以上的并发连接,如果它们不能,那是因为你选择了错误的软件,而不是底层硬件的问题。

问题核心:不要让OS内核执行所有繁重的任务:将数据包处理、内存管理、处理器调度等任务从内核转移到应用程序高效地完成,让诸如Linux这样的OS只处理控制层,数据层完全交给应用程序来处理。

比如微软的DPDK就是设计了数据面快速路径等。

1、要知道linux内核一开始就是一个分时系统,最重要的是保证公平性,CFS算法。而我们的实际任务可能会有差别,所以最好把调度让应用程序完成,减轻内核调度的负担以及无意义的调度;
2、核绑定,任务可能会在不同的CPU核上运行,尤其现在是NUMA架构,会导致上下文的切换,以及cache命中率的问题;
3、数据包直接和应用层交互,Linux协议栈是复杂和繁琐的,数据包经过它无非会导致性能的巨大下降,并且会占用大量的内存资源。比如DPDK的网卡驱动就是在应用层,数据不经过内核。(当然发给自己的数据包肯定要经过内核)
4、大页机制的开启,减少地址转换开销。(4kb-2mb)

IO模型探索
一个典型的IO涉及到4次拷贝。
1.服务端请求接收,read过程。请求磁盘/网卡拷贝到内核page,再拷贝到用户处理;write,处理完后,拷贝到socket内核缓存,再拷贝到网卡发送;

设计服务端并发模型时,主要有如下两个关键点:
1)服务器如何管理连接,获取输入数据; (阻塞、非阻塞轮询、IO复用、信号)
2)服务器如何处理请求。
线程池、多线程、单线程

一个输入操作通常包括两个不同的阶段:
1)等待数据准备好;
2)从内核向进程复制数据。

阻塞IO:就是数据没来一直等,等待期间挂起线程。一般这种需要每个连接一个线程,很少用。优点是阻塞不占用CPU

非阻塞式 I/O:没有数据不会阻塞,而是一直轮询,实时性比较好。轮询将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低,所以一般 Web 服务器不使用这种 I/O 模型。

I/O 复用:不用CPU轮询了,一旦有准备好的数据会通知你。既没有CPU消耗,又使用一个描述符就可以监听大量socket节省资源;

信号驱动式 I/O 模型:当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
优点:线程并没有在等待数据时被阻塞,可以提高资源的利用率。IO复用没有数据时还会阻塞呢
信号驱动 I/O 尽管对于处理 UDP 套接字来说有用,即这种信号通知意味着到达一个数据报,或者返回一个异步错误。
但是,对于 TCP 而言,信号驱动的 I/O 方式近乎无用,因为导致这种通知的条件为数众多(因为有连接状态,比如连接完成、连接关闭、关闭连接请求发起,完成等等),每一个来进行判别会消耗很大资源,信号会产生中断上下文切换,与前几种方式相比优势尽失。

以上几种都是同步IO模型
异步 I/O 模型
同步IO指的是,第二阶段(数据到达后从内核拷贝到用户态需要自己参与)
为什么我们明明是想读取数据,什么非得要先发起一个select询问数据状态的请求,然后再发起真正的读取数据请求,能不能有一种一劳永逸的方式,我只要发送一个请求我告诉内核我要读取数据,然后我就什么都不管了,然后内核去帮我去完成剩下的所有事情?
效率最高,阻塞最少。
但是这需要操作系统做很多事情,但是这种方式还不稳定,目前只有windows的IOAP,linux后来也有AIO但是不稳定,所以主要用的还是IO复用技术

上面介绍的是如何处理连接,获取输入?
下面介绍如何处理请求?
1.每个连接一个线程。问题很多,并发量大时内存占用大;没有数据时会阻塞;线程频繁创建销毁也需要CPU;

2、Reactor 模式
最经典的就是IO复用+线程池。reactor单线程(redis就是这种)、reactor多线程、主从reactor模式(IO事件读取也会耗时,分给多个从reactor读)
这里要考虑瓶颈到底是IO事件分发部分,还是线程池处理部分。一般而言,是线程池,因为线程数目一般和核数相同或者略多4,8个。而我们一般会并发100,1000,10000个客户端,线程池是很忙的,就算从reactor减小了就绪IO的分配事件,只会导致线程池的队列变长。等待处理,还有可能导致队列溢出。
(这里重点来了,我们分析了**,IOaccept以及IO监听分发不是瓶颈,瓶颈是处理请求来不及,而最大的弊端在于:消息队列因为是多线程取消息,需要加锁的。所以大大降低了速度。如何降低线程池中消息队列消息取的速度非常重要。**)

Proactor 模型
我们发现reactor非阻塞同步模型。也就是等待IO就绪不阻塞,但是数据从内核拷贝到用户这段时间线程需要等待的。最好就是异步给操作系统来完成就好了。要使用异步IO
缺点:编程复杂性, Linux 系统下,Linux 2.6 才引入,目前异步 I/O 还不完善。内存使用,缓冲区在读或写操作的时间段内必须保持住,可能造成持续的不确定性,并且每个并发操作都要求有独立的缓存,相比 Reactor 模式,在 Socket 已经准备好读或写前,是不要求开辟缓存的;

什么是高并发
它通常是指单位时间内系统能够同时处理的请求数。简单点说,就是QPS

注意:高并发实际上就是对CPU资源的有效压榨。对于加密解密这种计算密集的任务,谈高并发没有意义,因为CPU一直在使用的。加机器就行了。
我们讨论的是IO密集型的包括网络数据库IO。

控制变量法
(这里可以谈一下为什么我们只做了服务器层,没有涉及到其它层,因为对于高并发来说,每一层都可能是瓶颈,我们主要研究服务层有哪些可以提高并发的方法)
在这里插入图片描述
要达到高并发,我们需要负载均衡、服务层、缓存层、持久层都是高可用、高性能的。甚至在第5步,我们也可以通过压缩静态文件、HTTP2推送静态文件、CDN来做优化,这里的每一层我们都可以写几本书来谈优化。
本文主要讨论服务层这一块,即图红线圈出来的那部分。不再考虑讲述数据库、缓存相关的影响。

在这里插入图片描述
这一步步的都是对CPU资源的层层压榨,让他最大程度不阻塞,不干别的事(上下文切换,数据拷贝等)
协程没有用户态到内核态的切换了,因为只是指针变了一下而已。这里说一下我们的协程和rust,go协程区别。
协程的另一个优点是同步写法实现异步性能。

反正测试的时候要尽可能让CPU利用率跑满,不要阻塞。并且确保它们是在做正确的事,不是在处理上下文切换,线程创建销毁,数据拷贝、地址翻译、CPU多核跨越访问、中断等。
C10M也是同样的思想。

找到问题,往往比解决问题更难。当我们理解了高并发之后,我们会发现高并发和高性能并不是编程语言限制了你,限制你的只是你的思想。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值