Gearman 使用案例(PHP–Yii2)
本文介绍如何在Yii2框架中利用Gearman进行异步发送电子邮件
过程描述:
- 首先安装Gearman,配置Yii2框架中的mailer以及log组件,log组件主要是为了方便输出日志;
- 创建gearman-worker,代码示例中GearmanController/actionWorker方法,在项目根目录下面执行 ./yii gearman/worker 即可启动worker;
- 创建gearman-client,代码示例中的GearmanController/actionClient方法,由于Gearman协议只支持传递字符串,所以在Client将mailer组件需要的参数配置成数组形式,再利用serialize()序列化,然后在worker中反序列化,执行命令 ./yii gearman/client 启动Client;
- 创建保护进程,防止worker被系统杀死,中断任务执行,如GearmanController/actionProtect方法, 执行命令 ./yii gearman/protect 即可启动5个worker,而且在worker被中止以后能够自动重启新的worker;
- 如果想防止protect进程也被kill掉,可以配合supervisor等进程管理工具实现对保护进程的管理,想看本人的另外一篇日志;
下面的代码示例部分与我自己的业务逻辑有关,请注意分辨
完整代码如下:
<?php
/**
* Created by PhpStorm.
* User: gamelife
* Date: 2016/3/29
* Time: 19:21
*/
namespace console\controllers;
use Yii;
use yii\base\Exception;
use yii\console\Controller;
use yii\helpers\Console;
use GearmanWorker;
use GearmanJob;
use GearmanClient;
use common\models\SystemTask;
use common\models\Task;
use common\models\TaskContent;
class GearmanController extends Controller
{
const MAX_CHILD_PROCESS_NUM = 5;
public function beforeAction($action)
{
defined('APP_BASE_PATH') or define('APP_BASE_PATH', dirname(dirname(__DIR__)));
return parent::beforeAction($action);
}
public function actionWorker()
{
$worker = new GearmanWorker();
$worker->addServer('127.0.0.1', 4730);
$worker->addFunction('sendemail', function(GearmanJob $job)
{
Yii::$app->mailer->useFileTransport = false;
Yii::$app->log->targets['file']->logFile = Yii::$app->getRuntimePath() . '/logs/' .'gearmanlog-' . date("Y_m_d") . '.log';
Yii::$app->log->targets['file']->logVars = [];
$emaildetails = unserialize($job->workload());
$task = Task::findOne($emaildetails['taskid']);
$systemtask = SystemTask::findOne($emaildetails['systemtaskid']);
try
{
$result = Yii::$app->mailer->compose($emaildetails['compose']['template'], $emaildetails['compose']['template_vars'])
->setTo($emaildetails['setto'])
->setSubject($emaildetails['setsubject'])
->send();
if ($result)
{
if ($task->use_default_template)
{
$taskcontent = TaskContent::findOne($emaildetails['taskcontentid']);
$taskcontent->used_count += 1;
$taskcontent->save();
}
Yii::error('任务' . $systemtask->id . '执行成功');
$systemtask->task_status = SystemTask::STATUS_SUCCESS;
} else {
Yii::error('任务' . $systemtask->id . '执行失败');
$systemtask->task_status = SystemTask::STATUS_FAIL;
}
} catch (Exception $e)
{
$systemtask->task_status = SystemTask::STATUS_FAIL;
Yii::error('任务' . $systemtask->id . '执行出错');
}
$systemtask->running = SystemTask::IS_NOT_RUNNING;
$task->save();
$systemtask->save();
});
while($worker->work())
{
if ($worker->returnCode() !== GEARMAN_SUCCESS)
{
echo "Something Wrong" . PHP_EOL;
}
}
}
public function actionProtect()
{
if (extension_loaded('pcntl'))
{
declare(ticks=1);
$child_process_num = 0;
$signal_handler = function($signal)
{
global $child_process_num;
switch($signal)
{
case SIGCHLD:
case SIGINT:
case SIGTERM:
$child_process_num--;
$this->stdout("进程被kill,中断(ctrl+c)或者退出" . PHP_EOL, Console::BOLD);
break;
default:
break;
}
};
pcntl_signal(SIGCHLD, $signal_handler);
pcntl_signal(SIGINT, $signal_handler);
pcntl_signal(SIGTERM, $signal_handler);
while(true)
{
$child_process_num++;
$parentpid = getmypid();
$forkpid = pcntl_fork();
if ($forkpid == -1)
{
$this->stdout('创建新的进程失败' . PHP_EOL, Console::BOLD);
exit(0);
} else if ($forkpid > 0)
{
$this->stdout('我是主进程,我的进程id是 ' . $forkpid . ' 我的父进程id是 ' . $parentpid . PHP_EOL, Console::BOLD);
if ($child_process_num >= static::MAX_CHILD_PROCESS_NUM)
{
pcntl_wait($status);
}
} else if ($forkpid == 0)
{
$this->stdout('我是子进程,我的父进程id是 ' . $parentpid." 我的进程id是 " . getmypid() . " ,当前共有{$child_process_num}个进程" . PHP_EOL, Console::BOLD);
pcntl_exec('/usr/bin/php', [ APP_BASE_PATH . '/yii', 'gearman/worker']);
}
pcntl_signal_dispatch();
sleep(rand(2,6));
}
} else {
throw new Exception('Please install PHP pcntl extension!');
}
}
public function actionClient()
{
$client = new GearmanClient();
$client->addServer('127.0.0.1', 4730);
$emailDetails = [
'compose' => [
'template' => Task::DEFAULT_TEMPLATE_NAME,
'template_vars' => [
'lovemessage' => 'Hello World',
],
],
'setto' => '',
'setsubject' => 'Gearman 测试邮件',
'taskid' => 8,
'systemtaskid' => 1,
'taskcontentid' => null,
];
$client->addTaskBackground('sendemail', serialize($emailDetails));
$client->runTasks();
}
}