从头学习swoole

Swoole是一个PHP的C扩展,可用来开发PHP的高性能高并发TCP/UDP Server。Swoole的网络IO部分基于epoll/kqueue事件循环,是全异步非阻塞的。 业务逻辑部分使用多进程同步阻塞方式来运行。这样既保证了Server能够应对高并发和大量TCP连接。又保证业务代码仍然可以简单的编写。
可以说swoole的出现为php续上了一命,swoole可谓是所有phper打家劫舍必备的武器…

一、swoole的进程管理模式

swoole进程分为三种角色:reactor线程、worker进程、taskworker进程

  • reactor线程

    负责维护客户端TCP连接、处理网络IO、处理协议、收发数据
    完全是异步非阻塞的模式
    全部为C代码,除Start/Shudown事件回调外,不执行任何PHP代码
    将TCP客户端发来的数据缓冲、拼接、拆分成完整的一个请求数据包
    Reactor以多线程的方式运行

  • Worker进程

    接受由Reactor线程投递的请求数据包,并执行PHP回调函数处理数据
    生成响应数据并发给Reactor线程,由Reactor线程发送给TCP客户端
    可以是异步非阻塞模式,也可以是同步阻塞模式
    Worker以多进程的方式运行

  • TaskWorker进程

    接受由Worker进程通过swoole_server->task/taskwait方法投递的任务
    处理任务,并将结果数据返回(使用swoole_server->finish)给Worker进程
    完全是同步阻塞模式
    TaskWorker以多进程的方式运行

  • 图解swoole进程关系:

    从头学习swoole_第1张图片

swoole中的reactor线程、worker进程、taskworker进程之间的关系类似于nginx与php-fpm之间的关系。

master进程启动后,创建reactor线程和manager进程。

reactore用于监听请求,并将请求下发给worker进程。(类似于nginx)

worker进程将耗时的处理转发给task进程处理。(类似于php-fpm)

manager进程用来管理worker进程和task进程,当worker、task进程处理了一定的连接(max_request)后自动重启,防止内存泄漏。

底层会为Worker进程、TaskWorker进程分配一个唯一的ID
不同的Worker和TaskWorker进程之间可以通过sendMessage接口进行通信

二、服务端

不管是tcp、udp、http,还是websocket、redis服务端,都是继承自swoole\server

  • 创建server对象

    $serv = new Server(string $host, int $port = 0, int $mode = SWOOLE_PROCESS,
        int $sock_type = SWOOLE_SOCK_TCP);
    

    $host参数用来指定监听的ip地址,如127.0.0.1,或者外网地址,或者0.0.0.0监听全部地址。当采用127.0.0.1或本机的外网地址时,表示只监听来自本机的客户端请求,适用于单体项目。
    IPv4使用 127.0.0.1表示监听本机,0.0.0.0表示监听所有地址
    IPv6使用::1表示监听本机,:: (相当于0:0:0:0:0:0:0:0) 表示监听所有地址

    $port监听的端口,如9501
    如果$sock_type为UnixSocket Stream/Dgram,此参数将被忽略
    监听小于1024端口需要root权限
    如果此端口被占用server->start时会失败
    $port参数可以设置为0,操作系统会随机分配一个可用的端口,进行监听。可以通过读取$server->port得到分配到的端口号。

    $mode运行的模式
    SWOOLE_PROCESS多进程模式(默认)
    SWOOLE_BASE基本模式

    $sock_type指定Socket的类型,支持TCP、UDP、TCP6、UDP6、UnixSocket Stream/Dgram 6种

  • 设置运行时的各项参数

    $serv->set(array(
        'reactor_num' => 2, //reactor thread num
        'worker_num' => 4,    //worker process num
        'backlog' => 128,   //listen backlog
        'max_request' => 50,
        'dispatch_mode' => 1,
    ));
    

    各项参数参考官方文档

  • 注册server的事件回调

    Swoole\Server是事件驱动模式,所有的业务逻辑代码必须写在事件回调函数中。当特定的网络事件发生后,底层会主动回调指定的PHP函数。

    • 事件执行顺序

    1、所有事件回调均在$server->start后发生
    2、服务器关闭程序终止时最后一次事件是onShutdown
    3、服务器启动成功后,onStart/onManagerStart/onWorkerStart会在不同的进程内并发执行,但3个事件的执行顺序是不确定的
    4、onReceive/onConnect/onClose在Worker进程中触发
    5、Worker/Task进程启动/结束时会分别调用一次onWorkerStart/onWorkerStop
    6、onTask事件仅在task进程中发生、onFinish事件仅在worker进程中发生

    • onStart

    函数原型:

    function onStart(Server $server);
    

    在此事件之前Server已进行了如下操作:

    1、已创建了manager进程
    2、已创建了worker子进程
    3、已监听所有TCP/UDP/UnixSocket端口,但未开始Accept连接和请求
    4、已监听了定时器

    接下来要执行:

    主Reactor开始接收事件,客户端可以connect到Server

    notice

    1、onStart回调中,仅允许echo、打印Log、修改进程名称。不得执行其他操作。onWorkerStart和onStart回调是在不同进程中并行执行的,不存在先后顺序。
    2、在onStart回调中可以使用异步和协程的API,但需要注意这可能会与dispatch_func和package_length_func存在冲突。请勿同时使用。
    3、onStart回调在return之前服务器程序不会接受任何客户端连接,因此可以安全地使用CURL等PHP提供的同步IO函数。

    可以在onStart回调中,将$serv->master_pid和$serv->manager_pid的值保存到一个文件中。这样可以编写脚本,向这两个PID发送信号来实现关闭和重启的操作。

    BASE模式下没有master进程,因此不存在onStart事件。请不要在BASE模式中使用使用onStart回调函数

  • onWorkerStart

    函数原型:

    function onWorkerStart(swoole_server $server, int $worker_id);
    

    此事件在Worker进程/Task进程启动时发生。这里创建的对象可以在进程生命周期内使用。

    onWorkerStart/onStart是并发执行的,没有先后顺序
    可以通过$server->taskworker属性来判断当前是Worker进程还是Task进程
    设置了worker_num和task_worker_num超过1时,每个进程都会触发一次onWorkerStart事件,可通过判断$worker_id区分不同的工作进程
    由 worker 进程向 task 进程发送任务,task 进程处理完全部任务之后通过onFinish回调函数通知 worker 进程。例如,我们在后台操作向十万个用户群发通知邮件,操作完成后操作的状态显示为发送中,这时我们可以继续其他操作。等邮件群发完毕后,操作的状态自动改为已发送。

    发生致命错误或者代码中主动调用exit时,Worker/Task进程会退出,管理进程会重新创建新的进程。这可能导致死循环,不停地创建销毁进程。
    如果想使用Reload机制实现代码重载入,必须在onWorkerStart中require你的业务文件,而不是在文件头部。在onWorkerStart调用之前已包含的文件,不会重新载入代码。
    可以将公用的、不易变的php文件放置到onWorkerStart之前。这样虽然不能重载入代码,但所有Worker是共享的,不需要额外的内存来保存这些数据。
    onWorkerStart之后的代码每个进程都需要在内存中保存一份。

    为Worker进程/Task进程重命名:

    $serv->on('WorkerStart', function ($serv, $worker_id){
        global $argv;
        if($worker_id >= $serv->setting['worker_num']) {
            swoole_set_process_name("php {$argv[0]} task worker");
        } else {
            swoole_set_process_name("php {$argv[0]} event worker");
        }
    });
    

    $worker_id是一个从[0-$worker_num)区间内的数字,表示这个Worker进程的ID
    $worker_id和进程PID没有任何关系,可使用posix_getpid函数获取PID

  • onManagerStart

    函数原型:

    void onManagerStart(swoole_server $serv);
    

    onManagerStart触发时,说明:

    Task和Worker进程已创建
    Master进程状态不明,因为Manager与Master是并行的,onManagerStart回调发生是不能确定Master进程是否已就绪
    在BASE模式下,如果设置了worker_num、max_request、task_worker_num参数,底层将创建manager进程来管理工作进程。因此会触发onManagerStart和onManagerStop事件回调。

  • onConnect

    函数原型:

    function onConnect(swoole_server $server, int $fd, int $reactorId);
    

    $server是Swoole\Server对象
    $fd是连接的文件标识符,发送数据/关闭连接时需要此参数
    $reactorId来自哪个Reactor线程

    onConnect/onClose这2个回调发生在worker进程内,而不是主进程。
    UDP协议下只有onReceive事件,没有onConnect/onClose事件

    有新的连接进入时,在worker进程中回调onConnect方法

  • onReceive

    函数原型:

    function onReceive(swoole_server $server, int $fd, int $reactor_id, string $data);
    

    $server,swoole_server对象
    $fd,TCP客户端连接的唯一标识符
    $reactor_id,TCP连接所在的Reactor线程ID
    $data,收到的数据内容,可能是文本或者二进制内容

    未开启swoole的自动协议选项,onReceive回调函数单次收到的数据最大为64K
    Swoole支持二进制格式,$data可能是二进制数据

    UDP协议,onReceive可以保证总是收到一个完整的包,最大长度不超过64K
    UDP协议下,$fd参数是对应客户端的IP,$reactor_id是客户端的端口和来源端口; 客户端ip等于long2ip(unpack(‘N’,pack(‘L’,$fd))[1]);
    TCP协议是流式的,onReceive无法保证数据包的完整性,可能会同时收到多个请求包,也可能只收到一个请求包的一部分数据
    swoole只负责底层通信,$data是通过网络接收到的原始数据。对数据解包打包需要在PHP代码中自行实现
    如果开启了eof_check/length_check/http_protocol,$data的长度可能会超过64K,但最大不超过$server->setting[‘package_max_length’]

  • onPacket

    函数原型:

    function onPacket(swoole_server $server, string $data, array $client_info);
    

    $server,swoole_server对象
    $data,收到的数据内容,可能是文本或者二进制内容
    $client_info,客户端信息包括address/port/server_socket 3项数据

    接收到UDP数据包时回调此函数,发生在worker进程中。

    服务器同时监听TCP/UDP端口时,收到TCP协议的数据会回调onReceive,收到UDP数据包回调onPacket,服务器设置的EOF或Length协议对UDP端口是不生效的,因为UDP包本身存在消息边界,不需要额外的协议处理。
    如果未设置onPacket回调函数,收到UDP数据包默认会回调onReceive函数

    onPacket回调可以通过计算得到onReceive的$fd和$reactor_id参数值。计算方法如下:

    $fd = unpack('L', pack('N', ip2long($addr['address'])))[1];
    $reactor_id = ($addr['server_socket'] << 16) + $addr['port'];
    
  • onClose

    函数原型:

    function onClose(swoole_server $server, int $fd, int $reactorId);
    

    $server 是swoole_server对象
    $fd 是连接的文件描述符
    $reactorId 来自那个reactor线程

    onClose回调函数如果发生了致命错误,会导致连接泄漏。通过netstat命令会看到大量CLOSE_WAIT状态的TCP连接。
    无论由客户端发起close还是服务器端主动调用$serv->close()关闭连接,都会触发此事件。因此只要连接关闭,就一定会回调此函数。
    这里回调onClose时表示客户端连接已经关闭,所以无需执行$server->close($fd)。代码中执行$serv->close($fd)会抛出PHP错误告警。
    swoole-1.9.7版本修改了$reactorId参数,当服务器主动关闭连接时,底层会设置此参数为-1,可以通过判断$reactorId < 0来分辨关闭是由服务器端还是客户端发起的。

  • onBufferFull

    函数原型:

    function onBufferFull(Swoole\Server $serv, int $fd);
    

    设置server->buffer_high_watermark选项来控制缓存区高水位线,单位为字节
    触发onBufferFull表明此连接$fd的发送队列已触顶即将塞满,这时不应当再向此$fd发送数据

    $server->set([
        'buffer_high_watermark' => 8 * 1024 * 1024,
    ]);
    
    $server->on('onBufferFull', function ($serv, $fd) {
    
    });
    
  • onBufferEmpty

    函数原型:

    function onBufferEmpty(Swoole\Server $serv, int $fd);
    

    设置server->buffer_low_watermark来控制缓存区低水位线
    触发此事件后,表明当前的$fd发送队列中的数据已被发出,可以继续向此连接发送数据了

  • onTask

    函数原型:

    function onTask(swoole_server $serv, int $task_id, int $src_worker_id, mixed $data);
    

    $task_id是任务ID,由swoole扩展内自动生成,用于区分不同的任务。$task_id和$src_worker_id组合起来才是全局唯一的,不同的worker进程投递的任务ID可能会有相同
    $src_worker_id来自于哪个worker进程
    $data 是任务的内容

    在onTask函数中 return字符串,表示将此内容返回给worker进程。worker进程中会触发onFinish函数,表示投递的task已完成。return的变量可以是任意非null的PHP变量。

    onTask函数执行时遇到致命错误退出,或者被外部进程强制kill,当前的任务会被丢弃,但不会影响其他正在排队的Task。

    4.2.12版本起,如果开启了 task_enable_coroutine 则回调函数原型是:

    $server->on('Task', function ($serv, Swoole\Server\Task $task) {
        //来自哪个`Worker`进程
        $task->workerId;
        //任务的编号
        $task->id;
        //任务的类型,taskwait, task, taskCo, taskWaitMulti 可能使用不同的 flags
        $task->flags;
        //任务的数据
        $task->data;
        //协程 API
        co::sleep(0.2);
        //完成任务,结束并返回数据
        $task->finish([123, 'hello']);
    });
    
  • onFInish

    函数原型:

    void onFinish(swoole_server $serv, int $task_id, string $data)
    

    $task_id是任务的ID
    $data是任务处理的结果内容

    task进程的onTask事件中没有调用finish方法或者return结果,worker进程不会触发onFinish

    执行onFinish逻辑的worker进程与下发task任务的worker进程是同一个进程

  • onPipeMessage

    函数原型:

    void onPipeMessage(swoole_server $server, int $src_worker_id, mixed $message);
    

    当工作进程收到由 sendMessage 发送的管道消息时会触发onPipeMessage事件。worker/task进程都可能会触发onPipeMessage事件。

    $src_worker_id消息来自哪个Worker进程
    $message消息内容,可以是任意PHP类型

  • onWorkerError

    函数原型:

    void onWorkerError(swoole_server $serv, int $worker_id, int $worker_pid, int $exit_code, int $signal);
    

    $worker_id 是异常进程的编号
    $worker_pid 是异常进程的ID
    $exit_code 退出的状态码,范围是 0~255
    $signal 进程退出的信号

    当Worker/Task进程发生异常后会在Manager进程内回调此函数。

    此函数主要用于报警和监控,一旦发现Worker进程异常退出,那么很有可能是遇到了致命错误或者进程CoreDump。通过记录日志或者发送报警的信息来提示开发者进行相应的处理。

    signal = 11:说明Worker进程发生了segment fault段错误,可能触发了底层的BUG,请收集core dump信息和valgrind内存检测日志,向我们反馈此问题
    exit_code = 255:说明Worker进程发生了Fatal Error致命错误,请检查PHP的错误日志,找到存在问题的PHP代码,进行解决
    signal = 9:说明Worker被系统强行Kill,请检查是否有人为的kill -9操作,检查dmesg信息中是否存在OOM(Out of memory)
    如果存在OOM,分配了过大的内存。检查Server的setting配置,是否创建了非常大的Swoole\Table、Swoole\Buffer等内存模块

  • onManagerStop

    函数原型:

    void onManagerStop(swoole_server $serv);
    

    当管理进程结束时调用它,onManagerStop触发时,说明Task和Worker进程已结束运行,已被Manager进程回收。

  • onWorkerStop

    函数原型:

    function onWorkerStop(swoole_server $server, int $worker_id);
    

    此事件在worker进程终止时发生。在此函数中可以回收worker进程申请的各类资源。

    进程异常结束,如被强制kill、致命错误、core dump 时无法执行onWorkerStop回调函数

  • onWorkerExit

    函数原型:

    function onWorkerExit(swoole_server $server, int $worker_id);
    

    仅在开启reload_async特性后有效。异步重启特性,会先创建新的Worker进程处理新请求,旧的Worker进程自行退出。

    Worker进程未退出,onWorkerExit会持续触发
    onWorkerExit仅在Worker进程内触发,Task进程不执行onWorkerExit

    旧的Worker进程,在退出时先会执行一次onWorkerStop事件回调,然后会在事件循环的每个周期结束时调用onWorkerExit通知Worker进程退出。
    在onWorkerExit中尽可能地移除/关闭异步的Socket连接,最终底层检测到Reactor中事件监听的句柄数量为0时退出进程。

  • onShutdown

    函数原型:

    function onShutdown(swoole_server $server);
    

    在此之前Swoole\Server已进行了如下操作:

    已关闭所有Reactor线程、HeartbeatCheck线程、UdpRecv线程
    已关闭所有Worker进程、Task进程、User进程
    已close所有TCP/UDP/UnixSocket监听端口
    已关闭主Reactor

    强制kill进程不会回调onShutdown,如kill -9
    需要使用kill -15来发送SIGTREM信号到主进程才能按照正常的流程终止
    在命令行中使用Ctrl+C中断程序会立即停止,底层不会回调onShutdown


  • 本主主要参考swoole官方文档,结合网上其他资料进行了一些整理和归纳,仅供参考。

你可能感兴趣的:(php,swoole)