无论对于B/S还是C/S,程序再怎么变,唯一不变的是用户不想等太久的躁动心情。所以服务端对于客户的请求,能有多快就多快。如果服务端需要执行很耗时的操作,就需要异步任务处理机制,保证当前的响应速度不受影响。

现在以下面的一个例子为引子:

<?php
$server = new Swoole\Server('0.0.0.0', 9501);
$server->set([
'max_wait_time'=>60,
'reload_async'=>true,
'worker_num'=>1,
'task_worker_num'=>1,
'task_max_request'=>100
]); //监听连接进入事件
$server->on('Connect', function ($server, $fd) { }); //监听数据接收事件
$server->on('Receive', function ($server, $fd, $from_id, $data) {
$server->task("receive to task:$data"); //将任务丢入异步任务列队
}); $server->on("task",function ($serv, $fd, $from_id, $data){
//在这里处理任务
$serv->finish("$data ok"); //处理完成后,将结果传给finish
}); $server->on("finish",function ($serv, $task_id, $data){
//处理任务完成后的事情
echo "finish $data\n";
}); //监听连接关闭事件
$server->on('Close', function ($server, $fd) { }); //启动服务器
$server->start();

上面这个例子,在之前的代码上增加了两个属性(task_worker_num和task_max_request)和两个事件(onTask和onFinish)。代码中同时设置了这四项,便可以启动swoole的异步任务处理。是不是很简单?

那么现在分别了解关于异步任务的新属性和事件:

新配置:

1) task_worker_num:配置 Task 进程的数量。【默认值:未配置则不启动 task】

配置此参数后将会启用 task 功能。所以 Server 必须要注册 onTask、onFinish 2 个事件回调函数。如果没有注册,服务器程序将无法启动。

注意:

Task 进程是同步阻塞的;

最大值不得超过 swoole_cpu_num() * 1000;

如果单个 task 的处理需要 100ms,那一个进程 1 秒就可以处理 1/0.1=10 个 task;则task 投递的速度,如每秒产生 2000 个 task,2000/10=200,就需要设置 task_worker_num => 200,启用 200 个 Task 进程;

Task 进程内不能使用 Swoole\Server->task 方法。

2) task_max_request:设置 task 进程的最大任务数。【默认值:0】

设置 task 进程的最大任务数。一个 task 进程在处理完超过此数值的任务后将自动退出。这个参数是为了防止 PHP 进程内存溢出。如果不希望进程自动退出可以设置为 0。

3) task_tmpdir:设置 task 的数据临时目录。【默认值:Linux /tmp 目录】

在 Server 中,如果投递的数据超过 8180 字节,将启用临时文件来保存数据。这里的 task_tmpdir 就是用来设置临时文件保存的位置。

注意:

底层默认会使用 /tmp 目录存储 task 数据,如果你的 Linux 内核版本过低,/tmp 目录不是内存文件系统,可以设置为 /dev/shm/;

task_tmpdir 目录不存在,底层会尝试自动创建

4)task_use_object:使用面向对象风格的 Task 回调格式。【默认值:false】

设置为 true 时,onTask 回调将变成对象模式。

//面向对象风格代码示例$server = new Swoole\Server('127.0.0.1', 9501);
$server->set([
'worker_num' => 1,
'task_worker_num' => 3,
'task_use_object' => true,
]);
$server->on('receive', function (Swoole\Server $server, $fd, $tid, $data) {
$server->task(['fd' => $fd,]);
});
$server->on('Task', function (Swoole\Server $server, Swoole\Server\Task $task) {
//此处$task是Swoole\Server\Task对象
$server->send($task->data['fd'], json_encode($server->stats()));
});
$server->start();

5) task_ipc_mode(进阶):设置 Task 进程与 Worker 进程之间通信的方式。【默认值:1】

这是一个进阶属性,正常情况下是不用设置,使用默认值即可。要了解这个属性,请先看文末的高级话题 :什么是IPC?

知道了什么是IPC后,这个属性可取以下三个值:

其中:

模式1:支持定向投递,可在 task 和 taskwait 方法中使用 dst_worker_id,指定目标 Task进程。dst_worker_id 设置为 -1 时,底层会判断每个 Task 进程的状态,向当前状态为空闲的进程投递任务。

模式2、3:消息队列模式使用操作系统提供的内存队列存储数据,未指定 mssage_queue_key 消息队列 Key,将使用私有队列,在 Server 程序终止后会删除消息队列。指定消息队列 Key 后 Server 程序终止后,消息队列中的数据不会删除,因此进程重启后仍然能取到数据。这两者的不同之处在于,模式2 支持定向投递,$serv->task($data, $task_worker_id) 可以指定投递到哪个 task 进程。模式3 是完全争抢模式, task 进程会争抢队列,将无法使用定向投递,task/taskwait 将无法指定目标进程 ID,即使指定了 $task_worker_id,在模式3 下也是无效的。另外:模式3 会影响 sendMessage 方法,使 sendMessage 发送的消息会随机被某一个 task 进程获取。

新函数

函数task:投递一个异步任务到 task_worker 池中。此函数是非阻塞的,执行完毕会立即返回。Worker 进程可以继续处理新的请求。

Swoole\Server->task(mixed $data, int $dstWorkerId = -1): int

$data:要投递的任务数据,必须是可序列化的 PHP 变量。

$dstWorkerId:可以指定要给投递给哪个 Task 进程,传入 ID 即可,范围参考 $worker_id;默认值:-1。

函数finish: 用于在 Task 进程中通知 Worker 进程,投递的任务已完成。此函数可以传递结果数据给 Worker 进程(即,触发worker进程的onFinish事件)。

Swoole\Server->finish(mixed $data)

$data:任务处理的结果内容

注意:·

finish 方法可以连续多次调用,Worker 进程会多次触发 onFinish 事件;

在 onTask 回调函数中调用过 finish 方法后,return 数据依然会触发 onFinish 事件;

Server->finish 是可选的。如果 Worker 进程不关心任务执行的结果,不需要调用此函数;

在 onTask 回调函数中 return 字符串,等同于调用 finish;

新事件

1) 事件onTask:在 task 进程内被调用。worker 进程可以使用 task 函数向 task_worker 进程投递新的任务。当前的 Task 进程在调用 onTask 回调函数时会将进程状态切换为忙碌,这时将不再接收新的 Task,当 onTask 函数返回时会将进程状态切换为空闲然后继续接收新的 Task。

function onTask(Swoole\Server $server, int $task_id, int $src_worker_id, mixed $data);

$server:Swoole\Server 对象

$task_id:执行任务的 task 进程 id【$task_id 和 $src_worker_id 组合起来才是全局唯一的,不同的 worker 进程投递的任务 ID 可能会有相同】

$src_worker_id:投递任务的 worker 进程 id

$data:任务的数据内容

注意:

如果开启了 task_enable_coroutine 则回调函数原型是:

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

在 onTask 函数中 return 字符串(return 的变量可以是任意非 null 的 PHP 变量),表示将此内容返回给 worker 进程。也可以通过 Swoole\Server->finish() 来触发 onFinish 函数,而无需再 return。此时worker 进程中会触发 onFinish 函数,表示投递的 task 已完成。

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

2)事件onFinish:在 worker 进程被调用,当 worker 进程投递的任务在 task 进程中完成时被触发。

function onFinish(Swoole\Server $server, int $task_id, mixed $data)

$server:Swoole\Server 对象;

$task_id:执行任务的 task 进程 id;

$data:任务处理的结果内容。

注意:

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

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

关于异步任务的注意点:

-使用消息队列通信,如果 Task进程 处理能力低于投递速度,可能会引起 Worker 进程阻塞。

-使用消息队列通信后 task 进程无法支持协程 (开启 task_enable_coroutine)。

----------- 高级话题分隔线--------------

什么是IPC

同一台主机上两个进程间通信 (简称 IPC) 的方式有很多种,在 Swoole 下我们使用了 2 种方式 Unix Socket 和 sysvmsg,下面分别介绍:

第一种:Unix Socket

全名 UNIX Domain Socket, 简称 UDS, 使用套接字的 API (socket,bind,listen,connect,read,write,close 等),和 TCP/IP 不同的是不需要指定 ip 和 port,而是通过一个文件名来表示 (例如 FPM 和 Nginx 之间的 /tmp/php-fcgi.sock),UDS 是 Linux 内核实现的全内存通信,无任何 IO 消耗。在 1 进程 write,1 进程 read,每次读写 1024 字节数据的测试中,100 万次通信仅需 1.02 秒,而且功能非常的强大,Swoole 下默认用的就是这种 IPC 方式

Swoole 下面使用 UDS 通讯有两种类型:SOCK_STREAM 和 SOCK_DGRAM,可以简单的理解为 TCP 和 UDP 的区别,当使用 SOCK_STREAM 类型的时候同样需要考虑 TCP 粘包问题。

当使用 SOCK_DGRAM 类型的时候不需要考虑粘包问题,每个 send() 的数据都是有边界的,发送多大的数据接收的时候就收到多大的数据,没有传输过程中的丢包、乱序问题,send 写入和 recv 读取的顺序是完全一致的。send 返回成功后一定是可以 recv 到。
在 IPC 传输的数据比较小时非常适合用 SOCK_DGRAM 这种方式,由于 IP 包每个最大有 64k 的限制,所以用 SOCK_DGRAM 进行 IPC 时候单次发送数据不能大于 64k,同时要注意收包速度太慢操作系统缓冲区满了会丢弃包,因为 UDP 是允许丢包的,可以适当调大缓冲区。

第二种:sysvmsg

即 Linux 提供的消息队列,这种 IPC 方式通过一个文件名来作为 key 进行通讯,这种方式非常的不灵活,实际项目使用的并不多,不做过多介绍。此种 IPC 方式只有两个场景下有用:

1)防止丢数据,如果整个服务都挂掉,再次启动队列中的消息也在,可以继续消费,但同样有脏数据的问题。

2)可以外部投递数据,比如 Swoole 下的 Worker进程通过消息队列给 Task进程投递任务,第三方的进程也可以投递任务到队列里面让 Task 消费,甚至可以在命令行手动添加消息到队列。

---------------------------  我是可爱的分割线  ----------------------------

最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

Swoole从入门到入土(5)——TCP服务器[异步任务]的更多相关文章

  1. 【Swoole】简单安装与创建TCP服务器

    pecl install swoole PHP的异步.并行.高性能网络通信引擎,使用纯C语言编写,提供了php语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据 ...

  2. php的异步非阻塞swoole模块使用(一)实现简易tcp服务器--服务端

    绑定tcp服务器的地址 $swserver = new swoole_server("127.0.0.1",9501); 设置tcp服务器装机容量(太危言耸听了-其实就是设置属性) ...

  3. HTTP从入门到入土(3)——TCP三次握手

    TCP三次握手 客户端与服务器之间互相发送HTTP请求响应之前需要先进行TCP连接,因为HTTP是一个无连接.无状态协议,不存在连接的概念,只有请求和响应的概念.而请求和响应实际上只是数据包,他们需要 ...

  4. php的异步非阻塞swoole模块使用(一)实现简易tcp服务器--客户端

    //实例化一个swoole客户端 $swclient = new swoole_client(SWOOLE_SOCK_TCP); //建立连接---如果连接无效则退出 )){ echo "连 ...

  5. Swoole学习(七)Swoole之异步TCP服务器的创建

    环境:Centos6.4,PHP环境:PHP7 <?php //创建TCP服务器 /** * $host 是swoole需要监听的ip,如果要监听本地,不对外服务,那么就是127.0.0.1;如 ...

  6. Swoole学习(二)Swoole之TCP服务器的创建

    环境:Centos6.4,PHP环境:PHP7 <?php //创建TCP服务器 /** * $host 是swoole需要监听的ip,如果要监听本地,不对外服务,那么就是127.0.0.1;如 ...

  7. swoole 创建tcp服务器

    server.php <?php /** * 创建tcp服务器 * Date: 2019/1/15 */ $serv = new swoole_server('127.0.0.1', 9501) ...

  8. Swoole系列(三):建立TCP服务器并发送数据测试

    <?php // 建立tcp服务器下 $host = '0.0.0.0'; $port = 9501; $serv = new swoole_server($host,$port); $serv ...

  9. 18-ESP8266 SDK开发基础入门篇--TCP 服务器 RTOS版,串口透传,TCP客户端控制LED

    https://www.cnblogs.com/yangfengwu/p/11112015.html 先规定一下协议 aa 55 02 01 F1 4C 控制LED点亮  F1 4C为CRC高位和低位 ...

  10. 17-ESP8266 SDK开发基础入门篇--TCP服务器 RTOS版,小试牛刀

    https://www.cnblogs.com/yangfengwu/p/11105466.html 现在开始写... lwip即可以用socket 的API  也可以用 netconn  的API实 ...

随机推荐

  1. JavaScript - input 上传图片 并展示 (食用简单)

    <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF ...

  2. 【面试题精讲】Mysql如何实现乐观锁

    有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 文章更新计划 系列文章地址 在 MySQL 中,可以通过使用乐 ...

  3. [转帖]在麒麟linux上安装Postgresql12.5

    https://jimolonely.github.io/tech/linux/install-postgresql-kylin/ 本文主要实践在麒麟V10版本上通过源码编译安装PostgreSQL1 ...

  4. 【转帖】纳尼,mysqldump导出的数据居然少了40万?

    0.导读 用mysqldump备份数据时,加上 -w 条件选项过滤部分数据,发现导出结果比实际少了40万,什么情况? 本文约1500字,阅读时间约5分钟. 1.问题 我的朋友小文前几天遇到一个怪事,他 ...

  5. [转帖]linux下如何避免rsyslog系统日志不停打印到console

    背景:linux环境下,服务器由于某种异常导致rsyslog message不停打印到console控制台,影响我们正常使用. ps:我遇见的场景: 解决办法:1. vim /etc/rsyslog. ...

  6. [转帖]HotSpot 虚拟机对象探秘

    https://www.cnblogs.com/xiaojiesir/p/15593092.html 对象的创建 一个对象创建的时候,到底是在堆上分配,还是在栈上分配呢?这和两个方面有关:对象的类型和 ...

  7. 数字预失真(DPD)小试

    前言 射频功放的增益响应并非线性的,受到放大管饱和效应的影响,功放不可避免地出现非线性.甚至具有记忆效应的失真.这种非线性失真不仅产生高阶谐波,还会产生互调干扰,降低带内信噪比,影响带外信号.因此,需 ...

  8. Gin 框架之Cookie与Session

    目录 一.Cookie和Session的由来 二.Cookie简介 1. 什么是Cookie 2. Cookie规范 3. 安全性 4. Cookie 关键配置 三.Session简介 1. 什么是S ...

  9. 解决Chrome翻译无法使用

    截止2022年11月3日自己ping出的ip不可用了 可以用以下ip 172.217.215.90 172.253.115.90 142.250.126.90 142.250.10.90 142.25 ...

  10. Qt "有效且启用的储存库"问题

    传送门 : https://www.cnblogs.com/SaveDictator/p/8532664.html 看就完了, 反正我好了 https://mirrors.tuna.tsinghua. ...