运行流程图启动一个Swoole应用时,一共会创建2+n+m个进程,2个是Master进程,一个Manager进程,其中n是进程数工作进程,m是TaskWorker进程的数量。名词解释Master进程主进程,会创建Manager进程、Reactor线程、UDP包接收线程、心跳检测线程等线程Manger进程管理进程,该进程的作用是创建和管理所有Worker进程和TaskWorker进程。当子进程运行完毕后,管理器进程负责回收子进程,避免成为僵尸进程。并创建新的子进程当服务器关闭时,管理器进程会向所有子进程发送信号,通知子进程关闭服务服务器。当服务器重新加载时,管理器进程将一个接一个地关闭/重启子进程。在这个过程中运行。当Reactor线程从客户端接收到数据后,会将数据打包,通过管道发送给一个Worker进程。接受Reactor线程投递的请求包,执行PHP回调函数处理数据生成响应数据发送给Reactor线程,Reactor线程发送给TCP客户端。可以是异步非阻塞模式,也可以是同步阻塞模式。Worker可以使用多进程的方式来运行TaskWorker进程是一个特殊的工作进程,这个进程的作用是处理一些耗时的任务,以达到释放Worker进程的目的。通过swoole_server->task/taskwait方法接受Worker进程下发的任务处理任务,并将结果数据(使用swoole_server->finish)返回给Worker进程。完全是同步阻塞模式。TaskWorker以多进程的方式运行Reactor线程,实际上运行的是Linux。中间是一个epoll实例,在MacOS中是一个Kqueue实例,用于接受客户端连接和接收客户端数据。Swoole的主进程是一个多线程程序。有一组非常重要的线程,称为Reactor线程。它是真正处理TCP连接和发送和接收数据的线程。Swoole的主线程接受一个新的连接后,会将这个连接分配给一个固定的Reactor线程,这个线程负责监听这个socket。在socket可读时读取数据,进行协议解析,将请求投递给Worker进程。当套接字可写时向TCP客户端发送数据。负责维护客户端TCP连接,处理网络IO,处理协议,收发数据。该模式是完全异步和非阻塞的。它们都是C代码。除了Start/Shutdown事件回调,没有执行任何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可以紧密结合,提供更高级的使用方式。一个比较通俗的比喻,假设服务器是工厂,师傅是董事长,经理是CEO,反应堆是销售经理,接受客户的订单。工人是工人。当销售员接到订单时,工人就开始工作,生产客户想要的东西。而TaskWorker可以理解为管理员,可以帮助Worker处理杂务,让Worker专心工作。所谓的回调函数(CallBack)就像夹子打开的捕鼠器。我们设置当鼠标踩到捕鼠器上时,它会关闭夹子并捕捉鼠标。当我们放置捕鼠器时,捕鼠器实际上并没有抓住老鼠。这个设置是一个回调,不会立即执行,而是在遇到触发条件(事件)时执行。在上面的例子中,我们放置了3个捕鼠器(回调函数),我们只需要知道它会在特定的鼠标上执行(Event),当你踩到它时(当它发生时),执行我们期望的功能即可。底层会给Worker进程和TaskWorker进程分配一个唯一的ID。不同ID的Worker和TaskWorker进程可以通过sendMessage接口进行通信。运行周期程序全局周期在swoole_server->start之前创建对象,我们称之为程序全局生命周期。这些变量在程序启动后会一直存在,直到整个程序运行完毕才会被销毁。一些服务器程序在关闭/重启之前可能会持续运行数月甚至数年,因此程序全局期间的对象在这段时间内继续驻留在内存中。程序的全局对象占用的内存由Worker进程共享,不额外占用内存。这部分内存将在写入时分离(COW)。这些对象在Worker进程中写入时,会自动脱离共享内存,成为进程全局对象。程序的全局include/require代码必须在整个程序关闭时释放,reload无效。进程全局周期swoole有进程生命周期控制机制。当一个Worker子进程处理的请求数超过max_request配置时,会自动销毁。Worker进程启动后创建的对象(在onWorkerStart中创建的对象)在子进程的生命周期内常驻内存。它可以在onConnect/onReceive/onClose中访问。进程全局对象占用的内存在当前子进程内存堆中,不是共享内存。对该对象的修改只在当前Worker进程中有效。过程中包含/需要的文件将在重新加载后重新加载。SessionSession在onConnect之后创建,或者在第一次onReceive时创建,onClose时销毁。客户端连接进入后,创建的对象会常驻内存,直到客户端离开才会被销毁。swoole中session期间的对象直接驻留在内存中,不需要session_start等操作。可以直接访问对象,执行对象的方法。请求周期请求周期是指一次完整的请求,即onReceive收到请求后开始处理,直到返回结果并发送响应。本次循环创建的对象会在请求完成后销毁。swoole中的请求周期对象与普通PHP程序中的请求周期对象是一样的。在请求到来时创建,在请求结束时销毁。4PHP回调函数样式Anonymousfunction$server->on('Request',function($req,$resp)use($a,$b,$c){echo"helloworld";});可以使用传递参数给匿名函数classstaticmethodclassA{staticfunctiontest($req,$resp){echo"helloworld";}}$server->on('Request','A::Test');$server->on('Request',array('A','Test'));对象方法类A{functiontest($req,$resp){echo"helloworld";}}$object=newA();$server->on('Request',array($object,'test'));函数函数my_onRequest($req,$resp){echo"helloworld";}$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来捕获异常,否则会导致worker进程退出。不支持set_exception_handler,必须使用try/catch方法处理异常。工作进程不得共享同一个网络服务客户端,如Redis或MySQL。Redis/MySQL连接创建相关代码可以放在onWorkerStart回调函数中。异步编程异步程序要求代码不能包含任何同步阻塞操作。异步和同步代码不能混用。一旦应用程序使用任何同步阻塞代码,程序就会退化为同步模式。类/函数的重复定义新手很容易犯这个错误,因为Swoole常驻内存,加载类/函数定义文件后不会释放。所以在引入类/函数的php文件时必须使用include_once或者require_once,否则会出现cannotredeclarefunction/class的致命错误。进程隔离进程隔离也是很多新手经常遇到的问题。修改了全局变量的值,为什么不生效,原因是全局变量在不同的进程中,内存空间是隔离的,所以是无效的。因此,使用Swoole开发Server程序需要了解进程隔离问题。不同进程中的PHP变量是不共享的。即使是全局变量,如果在进程A中修改了它的值,在进程B中也会失效。如果需要在不同的Worker进程中共享数据,可以使用Redis、MySQL、files、SwooleTable、APCu、shmget等工具实现了不同进程的文件句柄是隔离的,所以在A进程中创建的Socket连接或打开的文件在B进程中是无效的,即使其fd发送给B进程也是不可用的
