Swoole进程模型

进程

什么是进程php

进程Process是计算机中的程序关于某数据集合上的一次运行活动,是系统分配资源和调度的基本单位,是操做系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体。在当代面向线程设计的计算机结构中,进程是线程的容器。简单来讲,程序是指令、数据以及其组织形式的描述,而进程则是程序的实体。前端

在操做系统中,进程表示正在运行的程序,例如在终端中使用PHP命令运行PHP脚本,此时就至关于建立了一个进程,这个进程会在系统中驻存,申请属于它本身的内存空间和系统资源,而且运行相应的程序。shell

$ php build.php
<?php //获取当前进程的PID echo posix_getpid(); //修改所在进程的名称 swoole_set_process_name("swoole process master"); //模拟持续运行100秒的程序 sleep(100);//持续运行100秒的目的是为了在进程中能够查看而不至于很快结束 

运行程序编程

$ php build.php
71

查看进程vim

$ ps aux | grep 71
root         1  0.0  0.1  18188  1712 pts/0    Ss+  11:07   0:00 /bin/bash
root        71  0.0  3.0 340468 30788 pts/2    S+   13:41   0:00 swoole process master
root        76  0.0  0.0  11112   940 pts/1    S+   13:42   0:00 grep 71

对于一个进程来讲,最核心的内容可分为两部分:一部分是它的内存,这个内存是在建立初始时从系统中分配的,进程中全部建立的变量都会存储在内存环境中。另外一部分是上下文环境, 进程是运行在操做系统中的,对于程序而言,它的运行依赖于操做系统分配的资源、操做系统的状态以及程序自身的状态,这些就构成了进程的上下文环境。数组

父子进程

 
父子进程
  • 子进程会复制父进程的内存空间和上下文环境
  • 子进程会复制父进程的IO句柄即fd描述符
  • 子进程的内存空间与父进程的内存空间是独立,是互不影响的。
  • 修改子进程的内存空间并不会修改父进程或其余子进程的内存空间

例如:父进程经过fopen打开文件后获得一个IO句柄fd,子进程复制父进程后一样会获得这个fd。若是父进程和子进程同时对一个文件进行操做,会形成文件混乱,所以须要加互斥锁。缓存

例如:父进程中的变量x=1,父进程派生子进程后,子进程也会存在变量x=1,可是修改父进程中的变量x并不会影响子进程的变量x的值。安全

多进程

PHP是单进程执行的,在处理高并发时主要依赖于Web服务器或PHP-FPM的多进程管理以及进程的复用,但在PHP实现多进程尤为是后台PHP-CLI模式下处理大量数据或运行后台Deamon守护进程时,多进程的优点天然是最好的。性能优化

PHP的多线程也曾被人说起,但进程内多线程资源共享和分配问题难以解决,PHP有一个多线程过的扩展pthreads,它要求PHP环境必须是线程安全的。bash

多进程简单来讲就是多个进程同时执行多个任务,能够将耗时但又必须执行的查询分红多个子进程进行操做。

  • PHP多进程不支持PHP-FPM和CGI模式,只能经过PHP-CLI模式。
  • PHP多进程适用于定时任务执行,互斥且耗时的任务。

开发使用PHP多进程的场景也就是使用PHP-FPM,PHP-FPM做为PHP的多进程管理器,当使用Nginx做为WebServer时,来自客户端的请求会根据Nginx的路由配置,将以PHP为后缀的文件转发给PHP-FPM。当多个用户同时请求API时,PHP-FPM会开启多个PHP的处理进程进行处理。

检查PHP是否支持多进程扩展

$ php -m | grep pcntl

多进程的优点

PHP相比C、C++、Java少了多线程,PHP中只有多进程的方案,因此PHP中的全局变量和对象不是共享的,数据结构也不能跨进程操做,另外Socket文件描述符也不能共享...

多线程看似比多进程强大的多,多线程的缺陷也一样明显:

  • 数据同步时,要么牺牲性能处处加锁,要么使用地狱难度的无锁并发编程。
  • 当程序逻辑复杂后,锁会愈来愈难以控制。一旦死锁,程序基本上就完了。
  • 某个线程挂掉后全部的线程都会退出

相比较多线程,多进程拥有的优点是

  • 配合进程间通讯,基本能够实现任意数据共享。
  • 多进程不须要锁
  • 多进程能够共享内存的数据结构实现一些多线程的功能

对于并发服务器核心是IO,并不是大规模密集运算,高并发的服务器单机能维持10W链接,每秒能够处理3~5W笔消息收发。

普通的Web应用都是IO密集型的程序,瓶颈在MySQL上,因此体现不出PHP的性能优点。但在密集计算方面比C/C++、Java等静态编译语言相差几十倍甚至上百倍。

例如:使用多进程方式同时访问Web地址

$ vim multi.php
<?php echo "process begin: ".date("Y-m-d H:i:s").PHP_EOL; //初始化地址数组 $urls = [ "http://www.baidu.com", "http://www.360.com", "http://www.qq.com", "http://www.sina.com" ]; //初始化数组用于回收线程管道内容 $workers = []; //按照任务分配线程 for($i=0; $i<count($urls); $i++){ $url = $urls[$i]; //建立进程 $process = new swoole_process(function(swoole_process $worker) use($url){ //模拟执行耗时任务 file_get_contents($url); //sleep(1);//模拟耗时1秒 echo $url.PHP_EOL; }, true); //开启进程 $pid = $process->start(); $workers[$pid] = $process; } //打印管道内容 foreach($workers as $worker){ echo "pid : ".$worker->read(); } echo "process end: ".date("Y-m-d H:i:s").PHP_EOL; 

运行代码

$ php multi.php
process begin: 2019-06-22 16:19:48
pid : http://www.baidu.com
pid : http://www.360.com
pid : http://www.qq.com
pid : http://www.sina.com
process end: 2019-06-22 16:19:49

内存共享

进程之间是相互独立的,那么如何实现进程之间的通讯呢? 这里可使用共享内存的方式来实现。

共享内存ShareMemory是映射一段能被其余进程所访问的内存,这段共享内存由一个进程建立,但多个进程均可以访问。共享内存是最快的IPC方式,是针对其余进程之间通讯效率低下而专门设计的,它每每与其它通讯机制,如信号量配置使用以实现进程之间的同步和通讯。

共享内存是操做系统中比较特殊的内存,它并不依赖于任何进程, 也不属于任何进程。经过调用系统函数建立共享内存,并指定它的索引,也就是它的IDshmid,经过索引任何进程均可以在共享内存中申请内存空间并存储对应的值。

 
共享内存
  • 共享内存并不属于任何一个进程
  • 在共享内存中分配的内存空间能够被任何进程访问
  • 即便进程关闭,共享内存仍然能够继续保存在操做系统中。

查看操做系统中共享内存的分片

$ ipcs -m
------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     链接数  状态      
0x00000000 131072     jc         777        16384      1          目标       
0x00000000 327681     jc         600        67108864   2          目标       
0x00000000 262146     jc         777        8077312    2          目标

Swoole没有采用多线程模型而使用了多线程模型,在必定程度上减小了访问数据时加锁解锁的开销,但同时也引入了新的需求 共享内存。Swoole中为了更好的进行内存管理,减小频繁分配释放内存空间形成的损耗和内存碎片,Rango实际并实现了三种不一样功能的内存池分别时FixedPoolRingBufferMemoryGlobal

Swoole开发模式

对于传统PHP的Web开发而言,最经常使用的是LNMP架构。在LNMP架构中,当请求进入时,WebServer会将请求转交给PHP-FPM,PHP-FPM是一个进程池架构的FastCGI服务,内置了PHP解释器。PHP-FPM负责解释执行PHP文件并生成响应,最终返回给WebServer展示至前端。因为PHP-FPM自己是同步阻塞进程模型,在请求结束后会释放掉全部资源,包括框架初始化建立的一些列对象,从而致使PHP进程进入“空转”消耗大量CPU资源,最终致使单机的吞吐能力有限。

另外,在每次请求处理的过程都意味着一次PHP文件解析、环境设置等没必要要的耗时操做,当PHP进程处理完后就会销毁,没法在PHP程序中使用链接池等技术实现性能优化。

针对传统架构的问题,Swoole从PHP扩展下手,解决了上述问题。相比较传统的Web架构,Swoole进程模型最大的特色在于多线程Reactor模式处理网络请求,使其能轻松应对大量链接。

除此以外,Swoole是全异步非阻塞,所以占用资源少,程序执行效率高。在Swoole中程序运行只解析加载一次PHP文件,避免每次请求的重复加载。再者,Swoole进程常驻,使得链接池和请求之间的信息传递的实现成为可能。

使用Swoole开发时,须要开发人员对多进程的运行模式有着清晰的认识。另外,Swoole很容易形成内存泄露。在处理全局变量、静态变量的时候要当心,这种不会被GC清理的变量会存在整个生命周期中。若是没有正确的处理,很容易消耗完内存。而在PHP-FPM下,PHP代码执行完毕内存就会被彻底释放掉。

Swoole进程结构

LNMP架构中PHP是须要依赖Nginx这样的Web服务器以及PHP-FPM这样的多进程的PHP解析器。当一个请求到来时PHP-FPM会去建立一个新的进程去处理这个请求,在这种状况下,系统的开销很大程序上都用在建立和销毁进程上,致使了程序的响应效率并非很是高。

Swoole的强大之处在于进程模型的设计,即解决了异步问题,又解决了并发问题。

Swoole的进程可分为四种角色

  • Master进程
    保证Swoole机制运行,同时利用它建立Master主线程(负责接收链接、定时器等)和Reactor线程(处理链接并将请求分发给各个Worker进程)。
  • Manager进程
    Worker进程和Task进程均由Manager进程派生,Manager管理进程负责结束时回收子进程,避免僵尸进程的存在。
  • Worker进程
    用PHP回调函数处理由Reactor分发过来的请求数据,并生成响应数据发送给Reactor,由Reactor发送给TCP客户端。
  • Task进程
    接收由Worker进程分发给它的任务,以多进程方式运行,处理好后将结果返回给它的Worker进程。
 
Swoole进程

Swoole中采用了和PHP-FPM彻底不一样的架构,整个Swoole扩展能够分为三层:

 
Swoole进程模型

第1层:Master主进程

 
Master主进程

Master进程是Swoole的主进程,主要用于处理Swoole的核心事件驱动。Master主进程是一个多线程模型,拥有多个独立的Reactor线程。

Master主进程包含Master线程、Reactor线程、心跳检测线程、UDP收包线程。每一个Reactor子线程中都运行着一个epoll函数的实例,Swoole对于事件的监听都会在Reactor线程中实现,好比来自客户端的链接、本地通讯使用的管道、异步操做使用的文件以及文件描述符都会注册在epoll函数中。

Master主进程使用select/poll进行IO事件循环,Master主进程中的文件描述符只有几个,Reactor线程使用epoll,由于Reactor线程中会监听大量链接的可读事件,使用epoll能够支持大量的文件描述符。

HTTP服务器为例,Master主进程负责监听端口,而后接收新的链接,并将这个链接分配给一个Reactor线程,由这个Reactor线程监听此链接,一旦此链接可读时,它会读取数据并解析协议,而后将请求投递到Worker工做进程中去执行。

Master主进程内的回调函数

  • onStart 服务器启动时主进程的主线程回调此函数
  • onShutdown 服务器正常结束时发生

Master 线程

Swoole启动后Master主线程会负责监听服务器的socket,若是有新的链接accept,Master主线程会评估每一个Reactor线程的链接数量,并将此链接分配给链接最少的Reactor线程。这样作的好处是:

  • 每一个Reactor线程持有的链接数很是均衡,没有单个线程负载太高的问题。
  • 解决了惊群问题,尤为是拥有多个listen socket时,节约了线程唤醒和切换的开销。
  • 主线程接管了全部信号signal的处理,使Reactor线程运行中能够不被信号打断。

主线程Master在accept新的链接后,会将这个链接分配给一个固定的Reactor线程,并由这个线程负责监听此socket,在socket可读时读取数据,并进行协议解析,最后将请求投递到Worker进程。

 
Master主线程

Reactor线程

  • 负责维护客户端TCP链接、处理网络IO、处理协议、收发数据。
  • 彻底是异步非阻塞的模式
  • 所有都是C代码,除了Start/Shutdown事件回调外,不执行任何PHP代码。
  • TCP客户端发送来的数据缓冲、拼接、拆分为完整的请求数据包。
  • Reactor以多线程的方式运行

Swoole拥有多线程Reactor,因此能够充分利用多核,开启CPU亲和设置后,Reactor线程能够绑定单独的核,节省CPU Cache开销。

Reactor线程负责处理TCP链接,是收发数据的线程。Swoole的Master主线程在accept新的链接后,会将这个链接分配给一个固定的Reactor线程,并由这个线程负责监听此socket。在socket可读时读取数据,并进行协议解析,将请求投递到Worker工做进程。在socket可写时,将数据发送给TCP客户端。

Reactor线程负责维护客户端TCP链接、处理网络IO、处理协议、收发数据,它彻底是异步非阻塞的模式。
Reactor线程是全异步非阻塞的,即便Worker进程采用了同步模式,依然不响应Reactor线程的性能。在Worker进程组很繁忙的状态下,Reactor线程彻底不受影响,依然能够收发处理数据。

因为TCP是流式的没有边界,因此处理起来很麻烦。Reactor线程可使用EOF或者包头长度,自动缓存数据、组装数据包,等一个请求彻底收到后,再次递交给Worker。

Reactor所有是C代码,除了Start/Shutdown事件回调外,不执行任何PHP代码。它将TCP客户端发来的数据缓冲、拼接、拆分红完整的一个请求数据包。

综上所述,Master主进程中包含两个关键线程:Master主线程和Reactor线程,Master主线程用来处理accept()事件,建立新的socket fd,当它接收到新链接后会将新的socket链接放到Reactor线程的事件监听循环中,Reactor线程负责接收从客户端发送过来的数据,并按协议解析后经过管道pipe传递给Worker工做进程进行处理。Worker工做进程处理完毕后,会将结果经过管道pipe回传给Reactor线程,Reactor线程再按照协议将结果经过socket发送给客户端。能够看到Reactor线程负责数据的IO和传输,在Linux系统下这些IO事件都是经过epoll机制来处理的。

心跳包检测线程HeartbeatCheck

Swoole配置了心跳检测后心跳包线程会在固定事件内对全部以前在线的链接发送检测数据包。

UDP收包线程UdpRecv

接收并处理客户端UDP数据包

第2层:Manager管理进程

Swoole运行中会建立一个单独的管理进程,全部的Worker进程和Task进程都是从管理进程fork建立出来的。

Manager管理进程会监听全部子进程的退出事件,当Worker进程发生致命错误或运行生命周期结束时,Manager管理进程会回收此进程并建立新的进程。

Manager管理进程还能够平滑地重启全部工做进程Worker,以实现程序代码的从新加载。

Manager管理进程管理着Worker工做进程或Task任务进程,Worker工做进程或Task任务进程都被Manager管理进程fork建立并管理着。

Manager进程负责建立和管理下层的Worker进程组和Task进程组,Manager进程中不会运行任何用户层面的业务逻辑,仅仅只作进程的管理和分配。

Manager进程会fork建立出指定数量的Worker进程和Task进程。

Manager进程的工做职责

  • Worker工做进程和Task任务进程都是由Manager管理进程fork建立并管理的
  • 子进程结束运行时,Manager管理进程负责回收子进程,以免成为僵尸进程,并建立新的子进程。
  • 服务器关闭时,Manager管理进程发送信号给全部子进程,并通知子进程关闭服务。
  • 服务器重启时,Manager管理进程会逐个关闭或重启子进程。

Manager进程内的回调函数

  • onManagerStart 当管理进程启动时调用
  • onManagerStop 当管理进程结束时调用
  • onWorkerError 当Worker进程或Task进程发生异常后会在Manager进程会回调此函数

第3层:工做进程

  • 工做进程主要用于处理客户端请求
  • 工做进程接收由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据。
  • 工做进程生成响应数据并发送给Reactor线程,由Reactor线程发送给TCP客户端。
  • 工做进程能够是异步非阻塞模式也能够是同步阻塞模式。
  • 工做进程以多进程的方式运行

与传统的半同步半异步服务器不一样是,Swoole的工做进程能够同步的也能够异步的。这样带来了工做进程相似于PHP-FPM进程,它接收由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据。工做线程生成响应数据并发送给Reactor线程,由Reactor线程发送给TCP客户端。工做线程能够是异步模式,也能够是同步模式。另外,工做线程以多进程的方式运行。

Swoole想要实现最好的性能就必须建立出多个工做进程帮助处理任务,可是工做进程必须fork操做,而fork操做又是不安全的。若是没有管理将会出现不少僵尸进程,进而影响服务器性能。同时工做进程被误杀或因为程序缘由会引发异常退出,为了保证服务的稳定性,须要从新建立工做进程。

工做进程可分为两类:Worker进程和Task进程

  • Worker进程是Swoole的主逻辑进程,用于处理来自客户端的请求。

  • Task进程是Swoole提供的异步工做进程,用于处理耗时较长的同步任务。

Worker工做进程

Worker工做进程接收Reactor线程投递过来的数据,执行PHP代码,而后生成数据并交给Reactor线程,由Reactor线程经过TCP将数据返回给客户端。若是是UDP,Worker工做进程会直接将数据发送给客户端。

Worker进程中执行的PHP代码,它等同于PHP-FPMPHP-FPM在处理异步操做时是很无力的,但Swoole提供的Task进程能够很好的解决这个问题。Worker进程能够将一些异步任务投递给Task进程,而后直接返回,处理其余由Reactor线程投递过来的事件。

Worker进程内的回调函数

  • onWorkerStart 当Worker工做进程或Task任务进程启动时触发
  • onWorkerStop 当Worker进程终止时触发
  • onConnect 当有新的链接进入时触发
  • onClose 当TCP客户端链接关闭后触发
  • onReceive 当接收到数据时触发
  • onPacket 当接收到UDP数据包是时触发
  • onFinish 当Worker工做进程投递的任务在task_worker中完成时,Task进程会经过finish()方法将任务处理的结果发送给Worker进程。
  • onWorkerExit 当开启reload_async特性后有效,即异步重启特性。
  • onPipeMessage 当工做进程收到由sendMessage发送的管道消息时触发

Task任务进程

  • 异步工做进程
  • 接收由Worker工做进程经过swoole_server->taskswoole_server->taskwait方法投递的任务。
  • 处理任务,并将结果数据返回给Worker工做进程swoole_server->finish
  • 同步阻塞模式
  • 以多进程的方式运行

Swoolen除了Reactor线程,Task任务工做进程是以异步的方式处理其它任务的进程,使用方式相似于Gearman。它接收由Worker进程经过swoole_server->task/taskwait方法投递的任务,而后处理任务,并将结果数据使用swoole_server->finish返回给Worker进程。Task以多进程的方式进行运行。

简单来讲,能够将Reactor理解为Nginx,将Worker理解为PHP-FPM。Reactor线程异步并行地处理网络请求,而后再转发给Worker工做进程中去处理(在回调函数中处理)。Reactor和Worker之间经过UnixSocket进行通讯。

Swoole除了Reactor线程,Worker工做进程还提供了Task进程池。目的是为了解决业务代码中,有些逻辑部分不须要立刻执行。利用Task进程池,能够方便的投递一个异步任务区执行。

Task进程以彻底同步阻塞的方式运行,一个Task进程在执行任务期间是不接受从Worker进程投递的任务的,当Task进程执行完任务后,会异步的通知Worker进程并告诉它任务已经完成。

Task进程内的回调函数

  • onTask 在Task线程内被调用,Worker进程可以使用swoole_server_task函数向Task进程投递新的任务。
  • onWorkerStart 在Worker或Task进程启动时触发
  • onPipeMessage 当工做进程收到由sendMessage发送的管道消息时触发
 
Swoole的进程

Swoole进程协做

  1. 当客户端主动连入服务器的时候,客户端其实是与Master主进程中的某个Reactor线程发生了链接。
  2. 当TCP三次握手成功后,由这个Reactor线程将链接成功的消息告知Manager管理进程,再由Manager管理进程转交给Worker工做进程,最终在Worker工做进程中触发onConnect事件对应的方法。
  3. 当客户端向服务器发送一个数据包的时候,首先接收到数据包的是Reactor线程,同时Reactor线程会完成组包,再将组装好的包交给Manager管理进程,由Manager管理进程转交给Worker工做进程,此时Worker工做进程触发onReceive事件。
  4. 若是Worker工做进程中作了处理操做后再使用Send方法将数据发回给客户端时,数据会沿着这个路径逆流而上。
  5. Task任务进程用来处理一些占用时间较长的业务,主要处理Worker工做进程中占用时间较长的任务。

形象来讲

  • Master主进程 = 业务窗口
  • Reactor线程 = 前台接待员
  • Manager管理进程 = 项目经理
  • Worker工做进程 = 工人

当在业务窗口办理业务时,若是用户不少,后边的用户须要排队等待服务,Reactor负责与客户直接沟通,对客户的请求进行初步的整理(传输层级别的整理,组包),而后Manager负责将业务分配给合适的Worker,如空闲的Worker,最终Worker负责实现具体的业务。

Swoole进程关系

Reactor和Worker与Task的关系,简单理解可认为:Reactor = Nginx、Worker = PHP-FPM

Reactor线程异步并行地处理网络请求,而后再转发给Worker工做进程中去处理。

Reactor线程和Worker工做进程之间经过socket进行通讯。

在PHP-FPM的应用中,常常会将一个任务异步投递到Redis等队列中,并在后台启动一些PHP进程异步地处理这些任务。

Swoole提供的Worker工做进程是一套更加完整的方案,它将任务投递、队列、PHP任务进程管理融为一体。经过底层的API实现异步任务的处理。另外,Task任务进程能够在任务执行完毕后,再返回一个结果反馈到Worker工做进程。

Swoole的Reactor、Worker、Task之间能够紧密的结合起来,提供更加高级的使用方式。

假设Server是一个工厂

  • Reactor:销售,接收客户订单。
  • Worker:工人,当销售接单后,Worker去工做生产出客户须要的东西。
  • Task:行政人员,帮助Worker干些琐事儿,让Worker专心工做。

底层会为Worker工做进程、Task任务进程分配一个惟一的ID,不一样的Worker和Task任务进程之间能够经过sendMessage接口进行通讯。

Swoole执行流程

 
Swoole执行流程
  1. 当客户端请求进入Master主进程后会被Master主线程接收到
  2. 将读写操做的监听注册到对应的Reactor线程中,并通知Worker工做进程处理onConnect,也就是接收到链接的回调。
  3. 客户端的数据会通知对应的Reactor线程并发送给Worker工做进程进行处理。
  4. 若是Worker工做进程投递任务,将数据经过管道发送给Task任务进程,Task任务进程处理完后会发送给Worker工做进程。
  5. Worker工做进程会通知Reactor线程发送数据给客户端。
  6. 当Worker工做进程出现异常时关闭,Manager管理进程会从新建立一个Worker工做进程,保证Worker工做进程的数量是固定的。
 
Swoole执行流程
相关文章
相关标签/搜索