nginx、swoole高并发原理初探

1、阅前热身

为了更加形象的说明同步异步、阻塞非阻塞,咱们以小明去买奶茶为例。react

一、同步与异步

①同步与异步的理解

同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式。程序员

  • 同步
    当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行sql

  • 异步:
    当一个异步调用发出去后,调用者不能当即获得调用结果的返回。数据库

异步调用,要想得到结果,通常有两种方式:
一、主动轮询异步调用的结果;
二、被调用方经过callback来通知调用方调用结果。编程

②:生活实例

同步买奶茶:小明点单交钱,而后等着拿奶茶;
异步买奶茶:小明点单交钱,店员给小明一个小票,等小明奶茶作好了,再来取。服务器

异步买奶茶,小明要想知道奶茶是否作好了,有两种方式:
一、小明主动去问店员,一会就去问一下:“奶茶作好了吗?”...直到奶茶作好。
二、等奶茶作好了,店员喊一声:“小明,奶茶好了!”,而后小明去取奶茶。swoole


二、阻塞与非阻塞

①阻塞与非阻塞的理解

阻塞与非阻塞的重点在于进/线程等待消息时候的行为,也就是在等待消息的时候,当前进/线程是挂起状态,仍是非挂起状态。网络

  • 阻塞
    阻塞调用在发出去后,在消息返回以前,当前进/线程会被挂起,直到有消息返回,当前进/线程才会被激活.多线程

  • 非阻塞
    非阻塞调用在发出去后,不会阻塞当前进/线程,而会当即返回。架构

②:生活实例

阻塞买奶茶:小明点单交钱,干等着拿奶茶,什么事都不作;
非阻塞买奶茶:小明点单交钱,等着拿奶茶,等的过程当中,时不时刷刷微博、朋友圈...


三、总结

经过上面的分析,咱们能够得知:

同步与异步,重点在于消息通知的方式;阻塞与非阻塞,重点在于等消息时候的行为。

因此,就有了下面4种组合方式

  • 同步阻塞:小明在柜台干等着拿奶茶;

  • 同步非阻塞:小明在柜台边刷微博边等着拿奶茶;

  • 异步阻塞:小明拿着小票啥都不干,一直等着店员通知他拿奶茶;

  • 异步非阻塞:小明拿着小票,刷着微博,等着店员通知他拿奶茶。


2、Nginx如何处理高并发

一、Apache面对高并发,为何很无力?

Apache处理一个请求是同步阻塞的模式。

image_1b2ianfo42ag28ghrgl4u18sa13.png-93.4kB

每到达一个请求,Apache都会去fork一个子进程去处理这个请求,直到这个请求处理完毕。

面对低并发,这种模式没什么缺点,可是,面对高并发,就是这种模式的软肋了。

  • 1个客户端占用1个进程,那么,进程数量有多少,并发处理能力就有多少,但操做系统能够建立的进程数量是有限的。

  • 多进程就会有进程间的切换问题,而进程间的切换调度势必会形成CPU的额外消耗。当进程数量达到成千上万的时候,进程间的切换就占了CPU大部分的时间片,而真正进程的执行反而占了CPU的一小部分,这就得不偿失了。

下面,举例说明这2种场景是多进程模式的软肋:

  • 及时消息通知程序
    好比及时聊天程序,一台服务器可能要维持数十万的链接(典型的C10K问题),那么就要启动数十万的进程来维持。这显然不可能。

  • 调用外部Http接口时
    假设Apache启动100个进程来处理请求,每一个请求消耗100ms,那么这100个进程能提供1000qps。

可是,在咱们调用外部Http接口时,好比QQ登陆、微博登陆,耗时较长,假设一个请求消耗10s,也就是1个进程1s处理0.1个请求,那么100个进程只能达到10qps,这样的处理能力就未免太差了。

注:什么是C10K问题?
网络服务在处理数以万计的客户端链接时,每每出现效率低下甚至彻底瘫痪,这被称为C10K问题。(concurrent 10000 connection)

综上,咱们能够看出,Apache是同步阻塞的多进程模式,面对高并发等一些场景,是很苍白的。


二、Nginx何以问鼎高并发?

传统的服务器模型就是这样,由于其同步阻塞的多进程模型,没法面对高并发。
那么,有没有一种方式,可让咱们在一个进程处理全部的并发I/O呢?
答案是有的,这就是I/O复用技术。

①、I/O复用是神马?

最初级的I/O复用

所谓的I/O复用,就是多个I/O能够复用一个进程。

上面说的同步阻塞的多进程模型不适合处理高并发,那么,咱们再来考虑非阻塞的方式。

采用非阻塞的模式,当一个链接过来时,咱们不阻塞住,这样一个进程能够同时处理多个链接了。

好比一个进程接受了10000个链接,这个进程每次从头至尾的问一遍这10000个链接:“有I/O事件没?有的话就交给我处理,没有的话我一会再来问一遍。
而后进程就一直从头至尾问这10000个链接,若是这1000个链接都没有I/O事件,就会形成CPU的空转,而且效率也很低,很差很差。

升级版的I/O复用

上面虽然实现了基础版的I/O复用,可是效率过低了。因而伟大的程序猿们日思夜想的去解决这个问题...终于!

咱们能不能引入一个代理,这个代理能够同时观察许多I/O流事件呢?

当没有I/O事件的时候,这个进程处于阻塞状态;当有I/O事件的时候,这个代理就去通知进程醒来?

因而,早期的程序猿们发明了两个代理---select、poll。

select、poll代理的原理是这样的:

当链接有I/O流事件产生的时候,就会去唤醒进程去处理。

可是进程并不知道是哪一个链接产生的I/O流事件,因而进程就挨个去问:“请问是你有事要处理吗?”......问了99999遍,哦,原来是第100000个进程有事要处理。那么,前面这99999次就白问了,白白浪费宝贵的CPU时间片了!痛哉,惜哉...

注:select与poll原理是同样的,只不过select只能观察1024个链接,poll能够观察无限个链接。

上面看了,select、poll由于不知道哪一个链接有I/O流事件要处理,性能也挺很差的。

那么,若是发明一个代理,每次可以知道哪一个链接有了I/O流事件,不就能够避免无心义的空转了吗?

因而,超级无敌、闪闪发光的epoll被伟大的程序员发明出来了。

epoll代理的原理是这样的:

当链接有I/O流事件产生的时候,epoll就会去告诉进程哪一个链接有I/O流事件产生,而后进程就去处理这个进程。

如此,多高效!

②、基于epoll的Nginx

有了epoll,理论上1个进程就能够无限数量的链接,并且无需轮询,真正解决了c10k的问题。

Nginx是基于epoll的,异步非阻塞的服务器程序。天然,Nginx可以轻松处理百万级的并发链接,也就无可厚非了。

3、swoole如何处理高并发以及异步I/O的实现

一、swoole介绍

swoole是PHP的一个扩展。
简单理解:swoole=异步I/O+网络通讯
PHPer能够基于swoole去实现过去PHP没法实现的功能。
具体请参考swoole官网:swoole官网


二、swoole如何处理高并发

①Reactor模型介绍

IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它自己不处理任何数据收发。只是能够监视一个socket(也能够是管道、eventfd、信号)句柄的事件变化。

注:什么是句柄?句柄英文为handler,能够形象的比喻为锅柄、勺柄。也就是资源的惟一标识符、资源的ID。经过这个ID能够操做资源。

image_1b2iad9fimm51tc5f4c13tj2009.png-63.3kB

Reactor只是一个事件发生器,实际对socket句柄的操做,如connect/accept、send/recv、close是在callback中完成的。

②swoole的架构

swoole采用 多线程Reactor+多进程Worker

swoole的架构图以下:

image_1b2iakmlo6gtba5m6ktfp9esm.png-125.1kB

swoole的处理链接流程图以下:

image_1b2ib3qhpkbajn4l2u2go1vc51g.png-62.5kB

当请求到达时,swoole是这样处理的:

请求到达 Main Reactor
        |
        |
Main Reactor根据Reactor的状况,将请求注册给对应的Reactor
(每一个Reactor都有epoll。用来监听客户端的变化)
        |
        |
客户端有变化时,交给worker来处理
        |
        |
worker处理完毕,经过进程间通讯(好比管道、共享内存、消息队列)发给对应的reactor。
        |
        |
reactor将响应结果发给相应的链接
        |
        |
    请求处理完成

由于reactor基于epoll,因此每一个reactor能够处理无数个链接请求。
如此,swoole就轻松的处理了高并发。

三、swoole如何实现异步I/O

基于上面的Swoole结构图,咱们看到swoole的worker进程有2种类型:
一种是 普通的worker进程,一种是 task worker进程。

worker进程是用来处理普通的耗时不是太长的请求;
task worker进程用来处理耗时较长的请求,好比数据库的I/O操做。

咱们以异步Mysql举例:

耗时较长的Mysql查询进入worker
            |
            |
worker经过管道将这个请求交给taskworker来处理
            |
            |
worker再去处理其余请求
            |
            |
task worker处理完毕后,处理结果经过管道返回给worker
            |
            |
worker 将结果返回给reactor
            |
            |
reactor将结果返回给请求方

如此,经过worker、task worker结合的方式,咱们就实现了异步I/O。

4、参考文章

Nginx 多进程模型是如何实现高并发的?
PHP并发IO编程之路
epoll 或者 kqueue 的原理是什么?
IO 多路复用是什么意思?

更多精彩,请关注公众号“聊聊代码”,让咱们一块儿聊聊“左手代码右手诗”的事儿。
图片描述