出品 | 漫云科技
整理 | 赵建平
⛳️ 异步任务
1.服务端处理任务
<?php
namespace app\admin\command;
use Swoole\Server;
use app\common\service\SwooleAsync;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Env;
class TestSwoole extends Command
{
protected function configure()
{
$this->setName('test_swoole')->setDescription('test');
}
/**
* @param Input $input
*/
protected function execute(Input $input, Output $output)
{
$serve_ip = Env::get('app.SWOOLE_IP', '127.0.0.1');
$serve_port = Env::get('app.SWOOLE_PORT', 9502);
$serv = new Server($serve_ip, $serve_port);
//设置异步任务的工作进程数量。
$serv->set([
'dispatch_mode' => 3, // 数据包分发策略,1=轮循模式/2=固定模式/3=抢占模式/4=IP分配
'daemonize' => 1, // 以守护进程的方式开启
'worker_num' => 8, //Worker 进程数,设置为 CPU 核数的 1-4 倍
'task_worker_num' => 20, // Task 进程的数量
'max_wait_time' => 60, // 进程收到停止服务通知后最大等待时间
'backlog' => 256, // 设置Listen队列长度
'max_request' => 80, // 每个进程最大接受请求数
]);
//此回调函数在worker进程中执行。
$serv->on('Receive', function($serv, $fd, $reactor_id, $data) {
$data = json_decode($data, true);
//投递异步任务
$task_id = $serv->task($data);
});
//处理异步任务(此回调函数在task进程中执行)。
$serv->on('Task', function ($serv, $task_id, $reactor_id, $data) {
// 因为swoole改动代码每次都需要重启,建议逻辑任务处理放在外部调用方便测试,改动后仍然需要重启才可以生效
// 例如:SwooleAsync::actionTask($data);
// 返回任务执行的结果
// $serv->finish("{$data} -> OK");
});
//处理异步任务的结果(此回调函数在worker进程中执行)。
$serv->on('Finish', function ($serv, $task_id, $data) {
echo "AsyncTask[{$task_id}] Finish: {$data}".PHP_EOL;
});
//启动服务器
$serv->start();
}
}
2.客户端client,用于投送任务,重启小技巧.
<?php
use Swoole\Client;
try {
$client = new Client(SWOOLE_SOCK_TCP);
// 第三个参数$timeout 超时时间单位:秒,默认值:0.5
if (!$client->connect('127.0.0.1', 9502, 0.5)) {
echo '任务错误: 无法连接TCP服务,请联系管理员!';
exit();
}
if (!$client->send(json_encode($arr))) {
echo '任务错误:任务投送失败,请联系管理员!';
exit();
}
//关闭连接
$client->close();
}catch (\Exception $e){
/*投送任务异常,大概率是服务挂了,此时执行启动命令
还可在适当位置杀死所有swoole进程,然后再启动
shell_exec('sudo fuser -k -n tcp 9502')
*/
if(strstr($e->getMessage(),"Swoole\Client::connect(): connect to server[127.0.0.1:9502] failed. Error: Connection refused")){
shell_exec('cd /www/wwwroot/根目录 && php think test_swoole');
}
return false;
}
⛳️ 高并发场景协程
1.客户端
<?php
try {
$ip = '127.0.0.1';
$port = 9501;
$handle = stream_socket_client("udp://{$ip}:{$port}", $errno, $errstr);
if (!$handle) {
echo '服务器异常' . $errstr;
}
fwrite($handle, $sendMsg . "\n");
$result = fread($handle, 1024);
fclose($handle);
$ret = json_decode($result, true); // 获取结果
if (empty($ret)) {
echo '服务异常';
}
// 根据返回结果$ret进行业务处理
} catch (Exception $e) {
echo '服务器异常' . $e->getMessage();
}
2.服务端
<?php
namespace app\admin\command;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Env;
class Swoole extends Command
{
protected function configure()
{
$this->setName('swoole')->setDescription('swoole');
}
/**
* @param Input $input
*/
protected function execute(Input $input, Output $output)
{
$serve_ip = Env::get('app.SWOOLE_IP', '127.0.0.1');
$serve_port = Env::get('app.SWOOLE_PORT', 9502);
// 构造函数的第三个参数,可以填 2 个常量值 -- SWOOLE_BASE 或 SWOOLE_PROCESS
// SWOOLE_PROCESS 多进程模式(默认),可以应对大量慢速连接,与客户端交互
// SWOOLE_BASE 基本模式,客户端连接之间不需要交互,可以使用 BASE 模式
// 构造函数的第四个参数,端口协议类型如:SWOOLE_SOCK_TCP/SWOOLE_SOCK_UDP
$server = new \Swoole\Server($serve_ip, $serve_port, SWOOLE_PROCESS, SWOOLE_SOCK_UDP);
$Chain = new channel(2000);
//监听数据接收事件
$server->on('Packet', function ($server, $data, $clientInfo) use ($Chain) {
$data = json_decode($data, true);
Coroutine::create(function () use ($Chain, $data) {
// 向通道中写入数据
$Chain->push($data);
});
Coroutine::create(function () use ($Chain, $data, &$ret) {
// 从通道中读取数据
$data = $Chain->pop();
try {
// 在这里进行逻辑任务处理
$ret = [1, '处理成功'];
} catch (\Exception $e) {
$ret = [0, '数据插入异常'];
}
});
$server->sendto($clientInfo['address'], $clientInfo['port'], json_encode($ret, 256));
});
//启动服务器
$server->start();
}
}
1.异步任务适用于复杂、大量数据处理,性能远远优于queue队列
2.协程适用于秒杀抢购等高并发场景