玩转 Codeigniter 框架 二 守护进程篇
守护进程(daemon),就是一个在后台一直运行的进程任务,我们可以用它来处理一些异步任务,比如邮件队列,短信队列,数据接入等等。在laravel里面我们可以直接交给job去处理,然后一个php artisan queue:work 就完事了,非常的方便。但是CI框架并没有这么的优化,因为CI是轻量级的框架,我理解的轻量级就是封装的方法比较少,框架为你做的比较少,自然就轻量级啊,哈哈哈。
1.首先我们定义该进程的执行命令 以及路径
nohup php /home/vagrant/code/project1/web/api/index.php cli deamon email_queue startup
换做 php 方法就是
$command = "nohup /usr/bin/php /home/vagrant/code/project1/web/api/index.php cli deamon email_queue startup" >> /home/vagrant/code/project1/application/logs/task_warning.log 2>&1 &";
$fp = popen($command, "r");
pclose($fp);
2.我们可以在controller目录下 增加一个文件夹如cli,来存放守护进程文件。
<?php
/**
* 邮件发送队列
*
* Class Email_queue
* User: UKNOW
* Date: 2020/6/3 15:55
*/
class Email_queue extends BASE_Controller
{
/**
* 进程停止标志redis key
* @var string
*/
private $stop_signal;
/**
* 进程最后执行时间 redis key
* @var string
*/
private $last_time;
/**
* 邮件缓存
* @var
*/
private $email_cache;
public function __construct()
{
parent::__construct();
//加载邮件处理类 此类中封装邮件发送的方法
$this->load->library('tools/BASE_Email', null, 'email_service');
//获取该进程停止redis key,方便管理后台 停止该进程任务
$this->stop_signal = $this->_get_redis_key('string:stop_signal');
//获取该进程最后一次执行的时间,方便监控这个进程是否运行异常
$this->last_time = $this->_get_redis_key('string:last_time');
//邮件缓存 防止短时间内发送重复的邮件
$this->email_cache = REDIS_PREFIX . 'string:cache:[MD5_CONTENT]';
}
/**
* 组装统一规范的相关redis键
*
* @author : UKNOW
*/
private function _get_redis_key($type)
{
// $argv是一个超级全局变量 该变量可以获取进程的 入口文件以及方法
global $argv;
$cli_params = $argv;
array_shift($cli_params);
return REDIS_PREFIX . $type . ':' . implode('_', $cli_params);
}
/**
* 邮件发送执行
*/
public function startup()
{
//如果是不是CLI模式则 停止执行
if (is_cli() == FALSE) exit;
//一个死循环
while (true) {
//判断任务是否开启,如果任务在管理后台关闭 则退出
$on_off = $this->redis->get($this->stop_signal);
if ($on_off == 'off') exit;
//记录最后执行时间,用于监控任务
$this->redis->set($this->last_time, time());
//获取待处理队列的数据
$email = $this->redis->rPop(REDIS_PREFIX . 'list:email');
if ($email) {
//拿到队列里面邮件 接收人 内容 抄送人等等数据信息
$email_arr = json_decode($email, true);
$email_title = $email_arr['title'];
$email_content = $email_arr['content'];
$email_tpl = $email_arr['tpl'];
$email_send_to = $email_arr['send_to'];
$email_send_cc = $email_arr['send_cc'];
$subject = $email_arr['subject'];
//如果发送人为空,则记录到日志里面,该日志也是一个异步队列,存到数据库,方便查阅
if (!$email_send_to) {
$log_data = [
'log_hash' => md5('INFO' . json_encode($email_arr)),
'level' => 'INFO',
'severity' => 'info',
'message' => '邮件缺少必要参数' . '标题:' . $email_title . ',内容:' . $email_content,
'filepath' => '',
'line' => 0,
];
$this->redis->lpush(REDIS_PREFIX . "list:error_log", json_encode($log_data));
continue;
}
//邮件是否重复判断,如果重复则不再继续执行
$params = [
'[MD5_CONTENT]' => md5($email)
];
$key = strtr($this->email_cache, $params);
if ($this->redis->get($key)) continue;
$this->redis->setex($key, 20 * 60, $email_content);
$view_param =[
'email_tpl' => $email_tpl,
'email_title' => $email_title,
'email_content' => $email_content
];
//整理邮件模版内容
$body = $this->load->view('email/templete', $view_param, true);
//调取邮件类的发送方法
$result = $this->email_service->send($email_send_to, $email_title, $subject, $body, null, $email_send_cc);
//如果发送失败 则记录日志
if ($result === FALSE) {
$log_data = [
'log_hash' => md5('INFO' . json_encode($email_arr)),
'level' => 'INFO',
'severity' => 'info',
'message' => '邮件发送失败' . '标题:' . $email_title . $result,
'filepath' => '',
'line' => 0,
];
$this->redis->lpush(REDIS_PREFIX . "list:error_log", json_encode($log_data));
}
} else {
// 如果没有从队列中获取数据,则3s睡眠,减少资源消耗,同时再次链接redis,防止链接丢失
sleep(3);
$this->redis->reconnect();
}
}
}
}
3.管理后台,查看执行中的进程,获取进程最后执行时间,关闭和开启进程
//获取 正在运行的进程
public function get_running_daemon()
{
$daemon_str = '';
/**
* 这个相当于 在linux命令航 执行 ps.....命令
* grep 'php' 筛选php相关进程,grep 'project1' 筛选本项目的相关进程,grep -v 去除,awk '{print $2...}' 是获取我们所想要的列的数据,大家可以自己执行下 ps -aux 结果看一下需要哪些列的数据
*/
$handler = popen("ps -aux | grep 'php' | grep 'project1' | grep -v grep | awk '{print $2,$3,$4,$5,$6,$12,$13,$14,$15,$16}'", "r");
while (!feof($handler)) {
$daemon_str .= fread($handler, '1024');
}
pclose($handler);
$daemon_arr = explode(PHP_EOL, $daemon_str);
$run_daemon_arr = array_map(function ($daemon) {
return explode(' ', $daemon);
}, array_filter($daemon_arr));
return $run_daemon_arr;
}
//获取每个进程 最后 执行时间
public function get_daemon_last_start_time()
{
$start_str = '';
$handler = popen("ps -eo pid,lstart | grep -v grep | grep -v PID", "r");
while (!feof($handler)) {
$start_str .= fread($handler, '1024');
} pclose($handler);
$start_arr = explode(PHP_EOL, $start_str);
$pid_start_arr = [];
foreach ($start_arr as $pkey => $pval) {
if (empty($pval)) {
continue;
}
$item_pid_time_arr = explode(' ', trim($pval));
if (is_array($item_pid_time_arr) && count($item_pid_time_arr) > 0) {
$pid = current($item_pid_time_arr);
if (empty($pid)) {
continue;
}
array_shift($item_pid_time_arr);
$pid_start_arr[$pid] = date('Y-m-d H:i:s', strtotime(implode(' ', $item_pid_time_arr)));
}
}
return $pid_start_arr;
}
/**
* 根据执行命令获取进程
* @param $process_tag
* @return false|string
*/
public function get_process_id($process_tag)
{
$fp = popen("ps -ef|grep '{$process_tag}$'|grep -v grep|awk '{print $2}'", "r");
$pid = fread($fp, 512);
pclose($fp);
return $pid;
}
/**
* 杀死进程
* @param $process_tag
* @return bool
*/
public function kill_process_id($process_tag)
{
if (! $process_tag) {
return false;
}
$fp = popen("ps -ef|grep '{$process_tag}$'|grep -v grep|awk '{print $2}'|xargs kill -9", "r");
pclose($fp);
return true;
}
bingo~
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: