运行流程图



图片描述

当启动一个Swoole应用时,一共会创建2+n+m个进程,2为一个Master进程和一个Manager进程,其中n为Worker进程数,m为TaskWorker进程数。

名词解释

Master进程

主进程,该进程会创建Manager进程、Reactor线程,UDP收包线程,心跳检测线程等线程

Manger进程

管理进程,该进程的作用是创建、管理所有的Worker进程和TaskWorker进程。
  • 子进程结束运行时,manager进程负责回收此子进程,避免成为僵尸进程。并创建新的子进程
  • 服务器关闭时,manager进程将发送信号给所有子进程,通知子进程关闭服务
  • 服务器reload时,manager进程会逐个关闭/重启子进程

Worker进程

工作进程,所有的业务逻辑代码均在此进程上运行。当Reactor线程接收到来自客户端的数据后,会将数据打包通过管道发送给某个Worker进程。
  • 接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据
  • 生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端
  • 可以是异步非阻塞模式,也可以是同步阻塞模式
  • Worker以多进程的方式运行

TaskWorker进程

一种特殊的工作进程,该进程的作用是处理一些耗时较长的任务,以达到释放Worker进程的目的。
  • 接受由Worker进程通过swoole_server->task/taskwait方法投递的任务
  • 处理任务,并将结果数据返回(使用swoole_server->finish)给Worker进程
  • 完全是同步阻塞模式
  • TaskWorker以多进程的方式运行

Reactor线程

实际运行Linux中是epoll实例,MacOS中为Kqueue实例,用于accept客户端连接以及接收客户端数据。

Swoole的主进程是一个多线程的程序。其中有一组很重要的线程,称之为Reactor线程。它就是真正处理TCP连接,收发数据的线程。

Swoole的主线程在Accept新的连接后,会将这个连接分配给一个固定的Reactor线程,并由这个线程负责监听此socket。

在socket可读时读取数据,并进行协议解析,将请求投递到Worker进程。在socket可写时将数据发送给TCP客户端。
  • 负责维护客户端TCP连接、处理网络IO、处理协议、收发数据
  • 完全是异步非阻塞的模式
  • 全部为C代码,除Start/Shudown事件回调外,不执行任何PHP代码
  • 将TCP客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包
  • Reactor以多线程的方式运行

运行机制

Swoole是php的扩展,一旦运行后就会接管PHP的控制权,进入事件循环。 

当某种IO(网络IO)事件发生时,Swoole 会回调用户设置的指定回调函数。
    
Swoole承担了底层网络事件的监听及各种底层事件处理,当收到请求时,会触发事件提醒,然后将控制权转交预先注册的事件回调函数,来进行后续的处理。

可以理解为Reactor就是nginx,Worker就是php-fpm。Reactor线程异步并行地处理网络请求,然后再转发给Worker进程中去处理。Reactor和Worker间通过UnixSocket进行通信。

Swoole提供的TaskWorker是一套更完整的方案,将任务的投递、队列、php任务处理进程管理合为一体。通过底层提供的API可以非常简单地实现异步任务的处理。

另外TaskWorker还可以在任务执行完成后,再返回一个结果反馈到Worker。

Swoole的Reactor、Worker、TaskWorker之间可以紧密的结合起来,提供更高级的使用方式。

一个更通俗的比喻,假设Server就是一个工厂,Master是董事长,Manager是CEO,那Reactor就是销售经理,接受客户订单。而Worker就是工人,当销售接到订单后,Worker去工作生产出客户要的东西。

而TaskWorker可以理解为行政人员,可以帮助Worker干些杂事,让Worker专心工作。
所谓的回调函数(CallBack) 就好比是张开了夹子的捕鼠器,我们设定当有老鼠踩到捕鼠器的时候,他会关闭夹子然后捉住老鼠,我们放置捕鼠器的时候,捕鼠器并没有真的抓老鼠。这个设定就是回调,他不立刻执行,会在遇到触发条件(事件)时执行,在上面的示例当中我们放置了3个捕鼠器(回调函数),我们只需要知道他会在特定老鼠(事件)踩到的时候(发生的时候)去执行我们期望的功能就好。
  • 底层会为Worker进程、TaskWorker进程分配一个唯一的ID
  • 不同的Worker和TaskWorker进程之间可以通过sendMessage接口进行通信

运行周期

程序全局期

 在swoole_server->start之前就创建好的对象,我们称之为程序全局生命周期。
 
 这些变量在程序启动后就会一直存在,直到整个程序结束运行才会销毁。
 
 有一些服务器程序可能会连续运行数月甚至数年才会关闭/重启,那么程序全局期的对象在这段时间持续驻留在内存中的。
 
 程序全局对象所占用的内存是Worker进程间共享的,不会额外占用内存。
 
 这部分内存会在写时分离(COW),在Worker进程内对这些对象进行写操作时,会自动从共享内存中分离,变为进程全局对象。
 
 程序全局期include/require的代码,必须在整个程序shutdown时才会释放,reload无效。

进程全局期

 swoole拥有进程生命周期控制的机制,一个Worker子进程处理的请求数超过max_request配置后,就会自动销毁。
 
 Worker进程启动后创建的对象(onWorkerStart中创建的对象),在这个子进程存活周期之内,是常驻内存的。
 
 onConnect/onReceive/onClose 中都可以去访问它。
 
 进程全局对象所占用的内存是在当前子进程内存堆的,并非共享内存。对此对象的修改仅在当前Worker进程中有效。
 
 进程期include/require的文件,在reload后就会重新加载。
 

会话期

 会话期是在onConnect后创建,或者在第一次onReceive时创建,onClose时销毁。一个客户端连接进入后,创建的对象会常驻内存,直到此客户端离开才会销毁。
 
 swoole中会话期的对象直接是常驻内存,不需要session_start之类操作。
 
 可以直接访问对象,并执行对象的方法。
 

请求期

 请求期就是指一个完整的请求发来,也就是onReceive收到请求开始处理,直到返回结果发送response。
 
 这个周期所创建的对象,会在请求完成后销毁。
 
 swoole中请求期对象与普通PHP程序中的对象就是一样的。
 
 请求到来时创建,请求结束后销毁。

4种PHP回调函数风格

匿名函数

$server->on('Request', function ($req, $resp) use ($a, $b, $c) {
       echo "hello world";
});
可使用use向匿名函数传递参数

类静态方法

class A
{
    static function test($req, $resp)
    {
        echo "hello world";
    }
}
$server->on('Request', 'A::Test');
$server->on('Request', array('A', 'Test'));

对象方法

class A
{
    function test($req, $resp)
    {
        echo "hello world";
    }
}

$object = new A();
$server->on('Request', array($object, 'test'));

函数

function my_onRequest($req, $resp)
{
    echo "hello world";
}
$server->on('Request', 'my_onRequest');

编程须知

注意事项

  • 不要在代码中执行sleep以及其他睡眠函数,这样会导致整个进程阻塞
  • 在swoole程序中禁止使用exit/die,如果PHP代码中有exit/die,当前工作的Worker进程、Task进程、User进程、以及swoole_process进程会立即退出。使用exit/die后Worker进程会因为异常退出, 被master进程再次拉起, 最终造成进程不断退出又不断启动和产生大量警报日志。
  • mt_rand随机数,在Swoole中如果在父进程内调用了mt_rand,不同的子进程内再调用mt_rand返回的结果会是相同的。所以必须在每个子进程内调用mt_srand重新播种。
  • while循环的影响,异步程序如果遇到死循环,事件将无法触发。异步IO程序使用Reactor模型,运行过程中必须在reactor->wait处轮询。如果遇到死循环,那么程序的控制权就在while中了,reactor无法得到控制权,无法检测事件,所以IO事件回调函数也将无法触发。
  • 可通过register_shutdown_function来捕获致命错误,在进程异常退出时做一些清理工作
  • PHP代码中如果有异常抛出,必须在回调函数中进行try/catch捕获异常,否则会导致工作进程退出
  • 不支持set_exception_handler,必须使用try/catch方式处理异常
  • Worker进程不得共用同一个Redis或MySQL等网络服务客户端,Redis/MySQL创建连接的相关代码可以放到onWorkerStart回调函数中。

异步编程

  • 异步程序要求代码中不得包含任何同步阻塞操作
  • 异步与同步代码不能混用,一旦应用程序使用了任何同步阻塞的代码,程序即退化为同步模式

类/函数重复定义

 新手非常容易犯这个错误,由于Swoole是常驻内存的,所以加载类/函数定义的文件后不会释放。因此引入类/函数的php文件时必须要使用include_once或require_once,否会发生cannot redeclare function/class 的致命错误。

进程隔离

 进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因就是全局变量在不同的进程,内存空间是隔离的,所以无效。所以使用Swoole开发Server程序需要了解进程隔离问题。
  • 不同的进程中PHP变量不是共享,即使是全局变量,在A进程内修改了它的值,在B进程内是无效的
  • 如果需要在不同的Worker进程内共享数据,可以用Redis、MySQL、文件、SwooleTable、APCu、shmget等工具实现
  • 不同进程的文件句柄是隔离的,所以在A进程创建的Socket连接或打开的文件,在B进程内是无效,即使是将它的fd发送到B进程也是不可用的

云天河9527
196 声望16 粉丝

闻道朝夕时,源法天地间