PHP项目CLI脚本与守护进程

wen PHP项目 1

本文目录导读:

PHP项目CLI脚本与守护进程

  1. CLI脚本基础
  2. 守护进程开发
  3. 高级守护进程特性
  4. 实际应用示例
  5. 运维与监控
  6. 最佳实践
  7. 性能优化

我来详细介绍PHP项目中CLI脚本和守护进程的开发与应用。

CLI脚本基础

1 基本结构

#!/usr/bin/env php
<?php
/**
 * CLI脚本示例
 * 
 * 使用方法: php script.php [options] [arguments]
 */
// 确保在CLI模式下运行
if (php_sapi_name() !== 'cli') {
    die("This script must be run from command line.\n");
}
// 错误处理设置
error_reporting(E_ALL);
ini_set('display_errors', 1);
// 参数解析
$options = getopt('h::v::', ['help::', 'verbose::']);
$args = array_slice($argv, 1);
// 帮助信息
function showHelp() {
    echo <<<HELP
用法: php script.php [选项] [参数]
选项:
  -h, --help     显示帮助信息
  -v, --verbose  显示详细输出
HELP;
    exit(0);
}
if (isset($options['h']) || isset($options['help'])) {
    showHelp();
}
// 主逻辑
$verbose = isset($options['v']) || isset($options['verbose']);
echo "Script started.\n";
if ($verbose) {
    echo "Arguments: " . implode(', ', $args) . "\n";
}

2 命令行参数处理

<?php
/**
 * 高级命令行参数处理
 */
class CommandLineParser {
    private $arguments = [];
    private $options = [];
    private $commands = [];
    public function __construct() {
        $this->parseArgs();
    }
    private function parseArgs() {
        global $argv;
        for ($i = 1; $i < count($argv); $i++) {
            $arg = $argv[$i];
            // 选项处理(--option=value 或 --option value)
            if (strpos($arg, '--') === 0) {
                $option = substr($arg, 2);
                if (strpos($option, '=') !== false) {
                    list($key, $value) = explode('=', $option, 2);
                    $this->options[$key] = $value;
                } else {
                    if ($i + 1 < count($argv) && !strpos($argv[$i + 1], '-')) {
                        $this->options[$option] = $argv[++$i];
                    } else {
                        $this->options[$option] = true;
                    }
                }
            }
            // 短选项处理(-v 或 -abc)
            elseif (strpos($arg, '-') === 0) {
                $flags = substr($arg, 1);
                for ($j = 0; $j < strlen($flags); $j++) {
                    $this->options[$flags[$j]] = true;
                }
            }
            // 命令处理
            elseif (empty($this->commands)) {
                $this->commands[] = $arg;
            }
            // 参数处理
            else {
                $this->arguments[] = $arg;
            }
        }
    }
    public function getOption($key, $default = null) {
        return $this->options[$key] ?? $default;
    }
    public function getCommand($index = 0) {
        return $this->commands[$index] ?? null;
    }
    public function getArgument($index = 0) {
        return $this->arguments[$index] ?? null;
    }
}
// 使用示例
$parser = new CommandLineParser();
$command = $parser->getCommand();
$verbose = $parser->getOption('verbose', false);

守护进程开发

1 基础守护进程框架

<?php
/**
 * 基础守护进程类
 */
class Daemon {
    private $pidFile;
    private $logFile;
    private $running = false;
    private $callback;
    public function __construct($pidFile = '/tmp/daemon.pid', $logFile = '/tmp/daemon.log') {
        $this->pidFile = $pidFile;
        $this->logFile = $logFile;
    }
    /**
     * 设置主循环回调
     */
    public function setCallback(callable $callback) {
        $this->callback = $callback;
    }
    /**
     * 启动守护进程
     */
    public function start() {
        if ($this->isRunning()) {
            die("Daemon is already running.\n");
        }
        // 创建子进程
        $pid = pcntl_fork();
        if ($pid == -1) {
            die("Fork failed.\n");
        } elseif ($pid) {
            // 父进程退出
            exit(0);
        }
        // 子进程继续执行
        // 创建新的会话
        if (posix_setsid() == -1) {
            die("Setsid failed.\n");
        }
        // 第二次fork
        $pid = pcntl_fork();
        if ($pid == -1) {
            die("Second fork failed.\n");
        } elseif ($pid) {
            exit(0);
        }
        // 保存PID
        file_put_contents($this->pidFile, posix_getpid());
        // 关闭标准输入输出
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);
        // 重定向到日志文件
        $logFile = fopen($this->logFile, 'a');
        if ($logFile) {
            ob_start(function($buffer) use ($logFile) {
                fwrite($logFile, $buffer);
                return '';
            });
        }
        $this->running = true;
        $this->log("Daemon started.");
        // 信号处理
        pcntl_signal(SIGTERM, [$this, 'handleSignal']);
        pcntl_signal(SIGINT, [$this, 'handleSignal']);
        // 主循环
        while ($this->running) {
            pcntl_signal_dispatch();
            if ($this->callback) {
                call_user_func($this->callback);
            }
            sleep(1);
        }
        $this->cleanup();
    }
    /**
     * 停止守护进程
     */
    public function stop() {
        if ($pid = $this->getPid()) {
            posix_kill($pid, SIGTERM);
            $this->log("Stopping daemon...");
        }
    }
    /**
     * 重启守护进程
     */
    public function restart() {
        $this->stop();
        sleep(1);
        $this->start();
    }
    /**
     * 检查是否运行
     */
    public function status() {
        if ($this->isRunning()) {
            echo "Daemon is running (PID: " . $this->getPid() . ")\n";
        } else {
            echo "Daemon is not running.\n";
        }
    }
    /**
     * 检查守护进程是否在运行
     */
    private function isRunning() {
        $pid = $this->getPid();
        if ($pid && posix_kill($pid, 0)) {
            return true;
        }
        if (file_exists($this->pidFile)) {
            unlink($this->pidFile);
        }
        return false;
    }
    /**
     * 获取PID
     */
    private function getPid() {
        if (file_exists($this->pidFile)) {
            return (int) file_get_contents($this->pidFile);
        }
        return null;
    }
    /**
     * 信号处理
     */
    public function handleSignal($signo) {
        switch ($signo) {
            case SIGTERM:
            case SIGINT:
                $this->running = false;
                break;
        }
    }
    /**
     * 日志记录
     */
    private function log($message) {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[{$timestamp}] {$message}\n";
        file_put_contents($this->logFile, $logMessage, FILE_APPEND);
    }
    /**
     * 清理
     */
    private function cleanup() {
        if (file_exists($this->pidFile)) {
            unlink($this->pidFile);
        }
        $this->log("Daemon stopped.");
    }
}

2 使用示例

<?php
// 创建CLI入口
if (php_sapi_name() !== 'cli') {
    die("This script must be run from command line.\n");
}
require_once 'Daemon.php';
$daemon = new Daemon('/tmp/myapp.pid', '/tmp/myapp.log');
// 设置主循环逻辑
$daemon->setCallback(function() {
    // 执行任务
    echo "Working...\n";
    // 处理数据库连接
    static $db = null;
    if ($db === null) {
        try {
            $pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            $db = $pdo;
        } catch (PDOException $e) {
            // 记录数据库连接错误
            return;
        }
    }
    // 执行具体任务
    try {
        $stmt = $db->query("SELECT * FROM queue WHERE status = 'pending' LIMIT 1");
        if ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            processJob($row);
        }
    } catch (PDOException $e) {
        // 重连数据库
        $db = null;
    }
});
// 根据命令行参数执行
$command = $argv[1] ?? 'help';
switch ($command) {
    case 'start':
        $daemon->start();
        break;
    case 'stop':
        $daemon->stop();
        break;
    case 'restart':
        $daemon->restart();
        break;
    case 'status':
        $daemon->status();
        break;
    default:
        echo "用法: php daemon.php {start|stop|restart|status}\n";
        exit(1);
}

高级守护进程特性

1 进程管理

<?php
/**
 * 进程管理器
 */
class ProcessManager {
    private $workers = [];
    private $maxWorkers = 5;
    private $tasks = [];
    public function __construct($maxWorkers = 5) {
        $this->maxWorkers = $maxWorkers;
    }
    /**
     * 添加任务
     */
    public function addTask($task) {
        $this->tasks[] = $task;
        $this->processTasks();
    }
    /**
     * 处理任务队列
     */
    private function processTasks() {
        while (count($this->workers) < $this->maxWorkers && !empty($this->tasks)) {
            $task = array_shift($this->tasks);
            $this->spawnWorker($task);
        }
    }
    /**
     * 创建子进程
     */
    private function spawnWorker($task) {
        $pid = pcntl_fork();
        if ($pid == -1) {
            // Fork失败
            return false;
        } elseif ($pid) {
            // 父进程
            $this->workers[$pid] = $task;
            return true;
        } else {
            // 子进程执行任务
            try {
                $result = call_user_func($task['callback'], $task['data'] ?? null);
                // 将结果写入共享内存或管道
                $this->writeResult($task['id'], $result);
            } catch (Exception $e) {
                $this->writeResult($task['id'], null, $e->getMessage());
            }
            exit(0);
        }
    }
    /**
     * 回收子进程
     */
    public function reapChildren() {
        while (($pid = pcntl_wait($status, WNOHANG)) > 0) {
            if (isset($this->workers[$pid])) {
                unset($this->workers[$pid]);
            }
        }
    }
    /**
     * 写入结果到共享存储
     */
    private function writeResult($taskId, $result, $error = null) {
        // 使用文件或共享内存存储结果
        $data = [
            'task_id' => $taskId,
            'result' => $result,
            'error' => $error,
            'time' => time()
        ];
        file_put_contents("/tmp/task_result_{$taskId}", serialize($data));
    }
    /**
     * 获取结果
     */
    public function getResult($taskId) {
        $file = "/tmp/task_result_{$taskId}";
        if (file_exists($file)) {
            $data = unserialize(file_get_contents($file));
            unlink($file);
            return $data;
        }
        return null;
    }
}

2 信号处理和优雅关闭

<?php
/**
 * 信号处理器
 */
class SignalHandler {
    private $signals = [];
    private $callbacks = [];
    private $debug = false;
    public function __construct($debug = false) {
        $this->debug = $debug;
        $this->registerDefaultSignals();
    }
    /**
     * 注册默认信号处理
     */
    private function registerDefaultSignals() {
        $signals = [
            SIGTERM => 'terminate',
            SIGINT => 'interrupt',
            SIGHUP => 'reload',
            SIGUSR1 => 'user1',
            SIGUSR2 => 'user2',
        ];
        foreach ($signals as $signal => $name) {
            $this->register($signal, function() use ($name) {
                $this->handleSignal($name);
            });
        }
    }
    /**
     * 注册信号处理器
     */
    public function register($signal, callable $callback) {
        if (!in_array($signal, $this->signals)) {
            $this->signals[] = $signal;
        }
        $this->callbacks[$signal] = $callback;
        pcntl_signal($signal, function($signo) use ($signal) {
            if (isset($this->callbacks[$signal])) {
                call_user_func($this->callbacks[$signal], $signo);
            }
        });
    }
    /**
     * 处理信号
     */
    private function handleSignal($name) {
        if ($this->debug) {
            echo "Received signal: {$name}\n";
        }
        switch ($name) {
            case 'terminate':
            case 'interrupt':
                $this->gracefulShutdown();
                break;
            case 'reload':
                $this->reloadConfiguration();
                break;
            default:
                // 自定义信号处理
                break;
        }
    }
    /**
     * 优雅关闭
     */
    private function gracefulShutdown() {
        if ($this->debug) {
            echo "Starting graceful shutdown...\n";
        }
        // 停止接受新工作
        // 完成当前工作
        // 清理资源
        // 关闭连接
        exit(0);
    }
    /**
     * 重新加载配置
     */
    private function reloadConfiguration() {
        if ($this->debug) {
            echo "Reloading configuration...\n";
        }
        // 重新读取配置文件
        // 更新运行时配置
        // 不中断服务
    }
    /**
     * 分发信号
     */
    public function dispatch() {
        pcntl_signal_dispatch();
    }
}

实际应用示例

1 消息队列消费者

<?php
/**
 * 消息队列消费者守护进程
 */
class QueueConsumerDaemon {
    private $redis;
    private $daemon;
    private $config;
    public function __construct($config) {
        $this->config = $config;
        $this->initRedis();
        $this->initDaemon();
    }
    private function initRedis() {
        $this->redis = new Redis();
        $this->redis->connect(
            $this->config['redis_host'] ?? '127.0.0.1',
            $this->config['redis_port'] ?? 6379
        );
    }
    private function initDaemon() {
        $this->daemon = new Daemon(
            $this->config['pid_file'] ?? '/tmp/queue_consumer.pid',
            $this->config['log_file'] ?? '/tmp/queue_consumer.log'
        );
        $this->daemon->setCallback([$this, 'processQueue']);
    }
    public function processQueue() {
        // 从队列中获取消息
        $message = $this->redis->brPop($this->config['queue_name'], 1);
        if ($message) {
            $data = json_decode($message[1], true);
            if ($data) {
                try {
                    $this->processMessage($data);
                } catch (Exception $e) {
                    // 记录错误并将消息放入死信队列
                    $this->redis->lPush(
                        $this->config['dead_letter_queue'] ?? 'dead_letter',
                        json_encode([
                            'original' => $data,
                            'error' => $e->getMessage(),
                            'time' => time()
                        ])
                    );
                }
            }
        }
    }
    private function processMessage($data) {
        // 处理具体消息
        switch ($data['type']) {
            case 'email':
                $this->sendEmail($data);
                break;
            case 'notification':
                $this->sendNotification($data);
                break;
            case 'webhook':
                $this->callWebhook($data);
                break;
        }
    }
}

2 任务调度器

<?php
/**
 * 定时任务调度器
 */
class TaskScheduler {
    private $tasks = [];
    private $running = true;
    public function addTask($name, callable $callback, $interval, $options = []) {
        $this->tasks[$name] = [
            'callback' => $callback,
            'interval' => $interval,
            'last_run' => 0,
            'enabled' => $options['enabled'] ?? true,
            'timeout' => $options['timeout'] ?? 3600
        ];
    }
    public function run() {
        while ($this->running) {
            foreach ($this->tasks as $name => &$task) {
                if (!$task['enabled']) {
                    continue;
                }
                $now = time();
                if ($now - $task['last_run'] >= $task['interval']) {
                    $task['last_run'] = $now;
                    // 使用进程隔离执行任务
                    $pid = pcntl_fork();
                    if ($pid == -1) {
                        // Fork失败
                        continue;
                    } elseif ($pid) {
                        // 父进程,设置超时监控
                        $this->monitorTask($name, $pid, $task['timeout']);
                    } else {
                        // 子进程执行任务
                        try {
                            call_user_func($task['callback']);
                        } catch (Exception $e) {
                            // 记录任务执行失败
                            $this->logTaskError($name, $e);
                        }
                        exit(0);
                    }
                }
            }
            // 回收子进程
            pcntl_wait($status, WNOHANG);
            sleep(1);
        }
    }
    private function monitorTask($name, $pid, $timeout) {
        // 创建超时监控
        $start = time();
        while (time() - $start < $timeout) {
            if (!posix_kill($pid, 0)) {
                // 进程已结束
                return;
            }
            usleep(100000); // 100ms
        }
        // 超时,强制终止
        posix_kill($pid, SIGKILL);
        $this->logTaskTimeout($name);
    }
}

运维与监控

1 健康检查

<?php
/**
 * 健康检查API
 */
class HealthCheck {
    private $checks = [];
    public function addCheck($name, callable $check) {
        $this->checks[$name] = $check;
    }
    public function runCheck() {
        $status = true;
        $results = [];
        foreach ($this->checks as $name => $check) {
            try {
                $result = call_user_func($check);
                $results[$name] = [
                    'status' => $result ? 'ok' : 'failed',
                    'message' => $result ? 'OK' : 'Failed'
                ];
                if (!$result) {
                    $status = false;
                }
            } catch (Exception $e) {
                $results[$name] = [
                    'status' => 'error',
                    'message' => $e->getMessage()
                ];
                $status = false;
            }
        }
        return [
            'status' => $status ? 'healthy' : 'unhealthy',
            'timestamp' => time(),
            'checks' => $results
        ];
    }
}

2 systemd服务配置

# /etc/systemd/system/php-daemon.service
[Unit]
Description=PHP Daemon Service
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php /var/www/app/daemon.php start
ExecStop=/usr/bin/php /var/www/app/daemon.php stop
ExecReload=/usr/bin/php /var/www/app/daemon.php restart
Restart=always
RestartSec=5
TimeoutStopSec=30
KillSignal=SIGTERM
[Install]
WantedBy=multi-user.target

最佳实践

1 错误处理

<?php
/**
 * 全局错误处理
 */
class ErrorHandler {
    public static function register() {
        set_error_handler([self::class, 'handleError']);
        set_exception_handler([self::class, 'handleException']);
        register_shutdown_function([self::class, 'handleShutdown']);
    }
    public static function handleError($severity, $message, $file, $line) {
        if (!(error_reporting() & $severity)) {
            return;
        }
        throw new ErrorException($message, 0, $severity, $file, $line);
    }
    public static function handleException($exception) {
        // 记录异常到日志
        error_log("Uncaught Exception: " . $exception->getMessage() . 
                  " in " . $exception->getFile() . 
                  ":" . $exception->getLine());
        // 发送告警
        // ...
        // 优雅关闭
        if (function_exists('posix_kill')) {
            posix_kill(posix_getpid(), SIGTERM);
        }
    }
    public static function handleShutdown() {
        $error = error_get_last();
        if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
            // 记录致命错误
            error_log("Fatal Error: " . $error['message'] . 
                      " in " . $error['file'] . 
                      ":" . $error['line']);
        }
    }
}

2 资源管理

<?php
/**
 * 资源管理器
 */
class ResourceManager {
    private static $instances = [];
    private $connections = [];
    private $files = [];
    /**
     * 注册资源
     */
    public function register($name, $resource, $type = 'connection') {
        $this->resources[$name] = [
            'resource' => $resource,
            'type' => $type,
            'created_at' => time()
        ];
    }
    /**
     * 释放所有资源
     */
    public function releaseAll() {
        foreach ($this->resources as $name => $info) {
            switch ($info['type']) {
                case 'connection':
                    if ($info['resource'] instanceof PDO) {
                        $info['resource'] = null;
                    }
                    break;
                case 'file':
                    if (is_resource($info['resource'])) {
                        fclose($info['resource']);
                    }
                    break;
                case 'stream':
                    if (is_resource($info['resource'])) {
                        stream_socket_shutdown($info['resource'], STREAM_SHUT_RDWR);
                    }
                    break;
            }
        }
        $this->resources = [];
    }
    /**
     * 获取资源使用统计
     */
    public function getStats() {
        $stats = [
            'total_resources' => count($this->resources),
            'memory_usage' => memory_get_usage(true),
            'peak_memory' => memory_get_peak_usage(true)
        ];
        return $stats;
    }
}

性能优化

1 内存优化

<?php
/**
 * 内存优化示例
 */
class MemoryOptimizer {
    /**
     * 批量处理,避免内存溢出
     */
    public function processLargeDataset($callback, $batchSize = 1000) {
        $offset = 0;
        while (true) {
            $batch = $this->getDataBatch($offset, $batchSize);
            if (empty($batch)) {
                break;
            }
            foreach ($batch as $item) {
                call_user_func($callback, $item);
            }
            // 强制垃圾回收
            gc_collect_cycles();
            $offset += $batchSize;
        }
    }
    /**
     * 使用生成器处理大数据
     */
    public function processWithGenerator($data) {
        foreach ($data as $item) {
            yield $this->processItem($item);
            // 每处理1000个项目清理一次
            if (++$count % 1000 === 0) {
                gc_collect_cycles();
            }
        }
    }
}

CLI脚本和守护进程开发需要注意内存管理、错误处理、信号处理等关键方面,选择适合的场景使用正确的方式来实现,可以提高项目的稳定性和可维护性。

抱歉,评论功能暂时关闭!