守護程式(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 協議》,轉載必須註明作者和本文連結