nginx、swoole高并发原理初探

一、阅前热身

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

1、同步与异步

①同步与异步的理解

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

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

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

异步调用,要想获得结果,一般有两种方式:
1、主动轮询异步调用的结果;
2、被调用方通过callback来通知调用方调用结果。

②:生活实例

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

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


2、阻塞与非阻塞

①阻塞与非阻塞的理解

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

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

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

②:生活实例

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


3、总结

通过上面的分析,我们可以得知:

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

所以,就有了下面4种组合方式

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

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

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

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


二、Nginx如何处理高并发

1、Apache面对高并发,为什么很无力?

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

每到达一个请求,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是同步阻塞的多进程模式,面对高并发等一些场景,是很苍白的。


2、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能够轻松处理百万级的并发连接,也就无可厚非了。

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

1、swoole介绍

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


2、swoole如何处理高并发

①Reactor模型介绍

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

注:什么是句柄?句柄英文为handler,可以形象的比喻为锅柄、勺柄。也就是资源的唯一标识符、资源的ID。通过这个ID可以操作资源。

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

②swoole的架构

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

swoole的架构图如下:

swoole的处理连接流程图如下:

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

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

因为reactor基于epoll,所以每个reactor可以处理无数个连接请求。 如此,swoole就轻松的处理了高并发。

3、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。

四、参考文章

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

进程与线程的一个简单解释

进程(process)和线程(thread)是操作系统的基本概念,但是它们比较抽象,不容易掌握。

最近,我读到一篇材料,发现有一个很好的类比,可以把它们解释地清晰易懂。

1.

计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。

2.

假定工厂的电力有限,一次只能供给一个车间使用。也就是说,一个车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务。

3.

进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

4.

一个车间里,可以有很多工人。他们协同完成一个任务。

5.

线程就好比车间里的工人。一个进程可以包括多个线程。

6.

车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

7.

可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所。里面有人的时候,其他人就不能进去了。这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。

8.

一个防止他人进入的简单方法,就是门口加一把锁。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。

9.

还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。

10.

这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。

不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。

11.

操作系统的设计,因此可以归结为三点:

(1)以多进程形式,允许多个任务同时运行;

(2)以多线程形式,允许单个任务分成不同的部分运行;

(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

(完)

http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

怎样理解阻塞非阻塞与同步异步的区别?

老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1 老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3 老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。
虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。
立等的老张,阻塞;看电视的老张,非阻塞。
情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。