swoole异步任务使用教程
学习swoole的总体流程先梳理一下。
1.swoole异步能解决什么样的问题?
2.swoole异步任务要如何使用?
3.使用时应注意什么细节?
1.swoole异步能解决什么样的问题?
相信大家在现在或未来的工作中,都会在遇到下面的一些问题。
问题1.测试同事说,哇这个接口好慢呀,平均响应时间都超过2秒了。能不能优化一下啊?然后自己一个,原来这里有个发邮件或者发短信验证码的操作。仔细一排查原来是因为第三方接口响应慢,导致自己的接口响应也慢。
这时swoole异步任务就很适合解决这种问题了。可以把这个操作投递到 TaskWorker 进程池中执行,投递后程序会立即返回,程序继续向下执行代码,不会拖慢当前的进程的处理速度。
这种场景只是举例,业务中如果还有类似的操作,都可以丢到异步任务中执行。
2.swoole异步任务要如何使用?
知道能解决什么问题了,那要怎么去使用呢?
首先先捋清楚使用的大概逻辑。逻辑:我们需要创建一个swoole服务,然后通过客户端把任务send到服务端,最后服务端接受到数据后,执行对应的操作,结束。
我结合自己的使用经历,总结了下面3个步骤。PS:自己先安装好swoole扩展哦
1.创建一个swoole服务
2.将任务投递到task进程池中
3.在task中编写自己的要执行的操作
2.1 创建一个swoole服务,其实就是初始化swoole\server对象
因为task任务是swoole\server对象的方法,所以我们需要先new 一个 swoole\server对象,并设置对象的一些配置和注册一些方法,就能调用了。
初始化时
必须要设置的点
- task_worker_num task进程的数量
- 注册onTask和onFinish这两个方法
下面是我封装的初始化swoole\server对象代码,可供参考。其中自行添加类对应的命名空间即可。
class Server
{
private $serv;
public function __construct()
{
//new一个swoole\server对象,可以new Swoole\Server('127.0.0.1', 9501)这样写,也可以new \swoole_server("127.0.0.1", 9501)这样写,别名而已,其实都是一样的。
//其中127.0.0.1是指定监听的 ip 地址。IPv4 使用 127.0.0.1 表示监听本机,0.0.0.0 表示监听所有地址一般监听本机即可
//$port监听的端口,如9501 0-1024之间,是系统默认保留的,所在建议从5000开始,一般使用默认的9501
$this->serv = new \swoole_server("127.0.0.1", 9501);
//配置参数
$this->serv->set(array(
'task_worker_num' => 200, //task进程的数量
//下面是一些常用的配置参数说明
//'worker_num' => 32, //worker进程数量一般设置为服务器CPU数的1-4倍1
//'daemonize' => 1, //111以守护进程执行11
//'max_request' => 2000,
//'dispatch_mode' => 3,//抢占模式,主进程会根据Worker的忙闲状态选择投递,只会投递给处于闲置状态的Worker
//"task_ipc_mode " => 1, // 使用Unix Socket通信,默认模式
//"log_file" => "log/taskqueueu.log" ,//日志
));
// bind事件对应的方法。
//当客户端发送数据时,会触发Receive函数。此方法中能接受到客户端send的参数
$this->serv->on('Receive', array($this, 'onReceive'));
//在task方法中,执行对应的操作
$this->serv->on('Task', array($this, 'onTask'));
//当任务执行完成后,会触发在finish事件。
$this->serv->on('Finish', array($this, 'onFinish'));
//启动swoole服务
$this->serv->start();
}
//$serv是swoole\server对象,$fd是连接的文件描述符,$from_id是TCP 连接所在的 Reactor 线程 ID,$data收到的数据内容,可能是文本或者二进制内容
public function onReceive($serv, $fd, $from_id, $data)
{
//把数据转发到task中
$serv->task($data);
}
public function onTask($serv, $task_id, $from_id, $data)
{
//暂时留空 2.3部分会完善
}
//
public function onFinish($serv, $task_id, $data)
{
//echo "Task {$task_id} finish\n";
//echo "Result: {$data}\n";
}
//调用这个runStart方法,即可创建一个swoole服务。我这边是在TP中啦,在public目录下,执行php index.php api/server/runStart 这段命令,即启动swoole。通过netstat -tunlp 看是否启动成功
public function runStart()
{
new Server();
}
}
2.2 将任务投递到task进程池中
通过客户端类的send方法即可投递到swoole服务中
class SwooleClient
{
private $client;
public function __construct()
{
$this->client = new Swoole\Client(SWOOLE_SOCK_TCP);
}
public function connect()
{
//9501要和swoole服务监听的端口号一致
if (!$this->client->connect("127.0.0.1", 9501, -1)) {
throw new \Exception(sprintf('Swoole Error: %s', $this->client->errCode));
}
}
//投递一个数据到swoole服务中
public function send($data)
{
if ($this->client->isConnected()) {
if (!is_string($data)) {
$data = json_encode($data);
}
//拼接"\r\n",是解决在循环场景下,投递任务可能会出现的tcp粘包问题。
return $this->client->send($data."\r\n");
} else {
throw new \Exception('Swoole Server does not connected.');
}
}
public function close()
{
$this->client->close();
}
}
在需要投递的场景时,new 一个客户端类,把数据投递到swoole服务中
$client = new Swoolecli();
$client->connect();
if ($client->send($value)) {
//成功,关闭链接
$client->close();
} else {
//异常处理
}
如果是在swoole的协程框架中。可以直接投递。因为是常驻内存的,内存中有初始化好的swoole对象。可以不用通过客户端的方式投递,直接$server->task($data)即可
比如
在think-swoole中可以通过下面的方式直接投递
$get = $this->request->get();
$code = mt_rand(1111,9999);
$phone_num = $get['phone_num'];
$task_data = [
'method' => 'sendSms',
'data' => [
'code' => $code,
'phone_num' => $phone_num
]
];
//投递任务
$this->app->swoole->task(json_encode($task_data));
在easyswoole中可以通过下面的方式直接投递
$server = ServerManager::getInstance()->getSwooleServer()->task(json_encode($task_data));
2.3 在task中编写自己的要执行的业务操作
//执行异步任务
public function onTask($serv, $task_id, $from_id, $data)
{
//一般发送过来的数据是json数据。
$data = json_decode($data,true);
//接受到task数据,进程业务逻辑处理。我一般喜欢新建一个执行异步任务的类,统一管理对应的操作。这种这个task里面,只写3句代码就行了。
$method = $data['method'];
return SwooleTask::getInstance()->$method($data['data'], $serv);
}
class SwooleTask
{
private static $obj = null;
public static function getInstance()
{
if (is_null(self::$obj)) {
self::$obj = new self();
}
return self::$obj;
}
/**
* Notes: 发送短信验证码
* User: 闻铃
* DateTime: 2021/9/9 下午4:06
* @param array $data
* @return bool
*/
public function sendSms($data = [])
{
if (empty($data)) {
return false;
}
return MsgCode::send($data['phone_num'], $data['code']);
}
/**
* 通过task机制推送数据给全部客户端
* @param $data
* @param $serv swoole server对象
*/
public function pushLive($data, $serv) {
//获取redis中的有序集合。
$clients = get_redis_obj()->set();
foreach($clients as $fd) {
$serv->push($fd, json_encode($data,JSON_UNESCAPED_UNICODE));
}
}
}
3.使用时应注意什么细节?
1.swoole服务的配置有很多,应根据业务场景和环境设置合适的配置。具体配置的话请参考官方文档,如下
全部的配置:https://wiki.swoole.com/#/ser...
下面是我总结了常用的配置
1.'worker_num' => 32, //一般设置为服务器CPU数的1-4倍 lscpu命令,cpus参数,即cpu核数
描述:指定启动的worker进程数。
说明:swoole是master-> n * worker的模式,开启的worker进程数越多,server负载能力越大,但是相应的server占有的内存也会更多。同时,当worker进程数过多时,进程间切换带来的系统开销也会更大。因此建议开启的worker进程数为cpu核数的1-4倍。
2.'ipc_mode' => 1
描述:设置进程间的通信方式。
说明:共有三种通信方式,参数如下
1 => 使用unix socket通信
2 => 使用消息队列通信
3 => 使用消息队列通信,并设置为争抢模式
3.'max_request' => 2000,
描述:每个worker进程允许处理的最大任务数。
说明:设置该值后,每个worker进程在处理完max_request个请求后就会自动重启。设置该值的主要目的是为了防止worker进程处理大量请求后可能引起的内存溢出。
4.'max_conn' => 10000
描述:服务器允许维持的最大TCP连接数
说明:设置此参数后,当服务器已有的连接数达到该值时,新的连接会被拒绝。另外,该参数的值不能超过操作系统ulimit -n的值,同时此值也不宜设置过大,因为swoole_server会一次性申请一大块内存用于存放每一个connection的信息。
5.'dispatch_mode' => 3
描述:指定数据包分发策略。
说明:共有三种模式,参数如下:
1 => 轮循模式,收到会轮循分配给每一个worker进程
2 => 固定模式,根据连接的文件描述符分配worker。这样可以保证同一个连接发来的数据只会被同一个worker处理
3 => 抢占模式,主进程会根据Worker的忙闲状态选择投递,只会投递给处于闲置状态的Worker
6.'task_worker_num' => 8
描述:服务器开启的task进程数。
说明:设置此参数后,服务器会开启异步task功能。此时可以使用task方法投递异步任务。
7.'task_max_request' => 10000
设置此参数后,必须要给swoole_server设置onTask/onFinish两个回调函数,否则启动服务器会报错。
描述:每个task进程允许处理的最大任务数。
说明:参考max_request task_worker_num
示例:
8.'task_max_request' => 10000
描述:每个task进程允许处理的最大任务数。
说明:参考max_request task_worker_num
9.'task_ipc_mode' => 2
描述:设置task进程与worker进程之间通信的方式。
说明:参考ipc_mode
10.'daemonize' => 1, //以守护进程 1或0
设置程序进入后台作为守护进程运行。
说明:长时间运行的服务器端程序必须启用此项。如果不启用守护进程,当ssh终端退出后,程序将被终止运行。启用守护进程后,标准输入和输出会被重定向到 log_file,如果 log_file未设置,则所有输出会被丢弃。
11.'log_file' => '/data/log/swoole.log'
指定日志文件路径
说明:在swoole运行期发生的异常信息会记录到这个文件中。默认会打印到屏幕。注意log_file 不会自动切分文件,所以需要定期清理此文件。
12.'heartbeat_check_interval' => 60
设置心跳检测间隔
说明:此选项表示每隔多久轮循一次,单位为秒。每次检测时遍历所有连接,如果某个连接在间隔时间内没有数据发送,则强制关闭连接(会有onClose回调)。
13.'heartbeat_idle_time' => 600
设置某个连接允许的最大闲置时间。
说明:该参数配合heartbeat_check_interval使用。每次遍历所有连接时,如果某个连接在heartbeat_idle_time时间内没有数据发送,则强制关闭连接。默认设置为heartbeat_check_interval * 2。
2.投递任务时,如何在同一个链接,短时间内多次投递数据,可能会有数据粘包问题,解决方式如下
客户端
在send时,拼接"\r\n"
比如 $this->client->send($data."\r\n");
服务端新增配置
1.自动分包
$server->set(array(
'open_eof_split' => true, //swoole底层实现自动分包。比较消耗cpu资源
'package_eof' => "\r\n", //设置后缀,一般为"\r\n"
));
或者
2.手动分包
$server->set(array(
'open_eof_check' => true, //打开EOF检测,每次包以EOF结尾,才send给服务端
'package_eof' => "\r\n", //设置EOF
))
需要在应用层代码中手动 explode("\r\n", $data) 来拆分数据包
效率比较高
如果对效率比较看重则使用手动分包,看场景选择合适的即可
4.项目实战截图
swoole异步任务的执行效率非常的高。在数据量较大的情况下,效率依然很高。
swoole的异步任务进程池效率有多高呢?手上刚好有个项目就是用swoole的异步任务的,每天异步处理6000w的新增数据量,也就6个小时左右,而且速度的瓶颈其实是卡在mysql层面。
学会了后多使用swoole异步任务,提高自己的接口响应效率吧。
如果觉得有帮助到你,就给我点个赞鼓励一下让我有更多的动力继续写文章吧
本文为夜雨闻铃原创文章,转载无需和我联系,但请注明文章出处。文章出处:夜雨闻铃的思否文章(https://segmentfault.com/u/ye...)