玩轉 Codeigniter 框架 二 守護程式篇

UKNOW發表於2020-06-11

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

相關文章