当前位置:首页>php>php异步任务实现,无需队列/无需Redis,接口不卡顿,高可用低成本方案

php异步任务实现,无需队列/无需Redis,接口不卡顿,高可用低成本方案

  • 2026-03-26 07:17:18
php异步任务实现,无需队列/无需Redis,接口不卡顿,高可用低成本方案

大家好,我是专注 PHP 实战干货的博主。

做业务开发时,「接口阻塞」是高频痛点:用户注册后发送短信/邮件要等3-5秒、导出大Excel要卡页面、生成报表要等加载… 这些场景用同步执行体验极差,但搭建 Redis/MQ 队列又太复杂(配置多、学习成本高)。

今天给大家带来 PHP7+ 异步任务极简版无第三方依赖、无需队列、一行调用、安全加固,原生 PHP 就能跑,彻底解决接口阻塞问题,小项目/快速迭代直接上线,生产级可用。

温馨提示:本文代码量有点长~但都是干货

一、核心痛点(为什么要做异步)

先看同步执行的坑:
<?php// 同步执行:用户注册后发送短信,接口要等短信接口响应完才返回functionregister($mobile{    // 1. 写入数据库    saveUser($mobile);    // 2. 发送短信(阻塞3-5秒)    sendSms($mobile);    // 3. 返回结果(用户要等3-5秒)    return ['code' => 200'msg' => '注册成功'];}

用户体验差、接口超时、服务器资源被占用… 而异步执行能让「发送短信」在后台跑,接口瞬间返回,核心就是:主线程快速响应,子进程处理耗时任务

二、核心原理(极简科普)

  1. 基于 PHP exec 函数
    开启独立子进程执行耗时任务,主线程不等待、直接返回;
  2. 安全封装
    过滤危险命令、白名单限制,防止 exec 注入;
  3. 无依赖
    不用 Redis、不用 MQ、不用框架,原生 PHP 就能跑;
  4. 轻量高效
    子进程后台运行,不占主线程内存,不卡死。
关键:exec 执行时加 > /dev/null 2>&1 & 让子进程后台运行,彻底脱离主线程。

三、异步任务完整封装类

单文件 AsyncTask.php,几十行核心代码,包含「异步执行、日志记录、失败重试、安全过滤」,复制就能用:
<?php/** * PHP 异步任务封装类(PHP 7.4+) * * 特性: *  - 无 Redis / MQ 依赖,原生 PHP 实现 *  - 并发超限时自动入队,不丢任务 *  - 队列由独立 Worker 消费,支持手动/自动触发 *  - 大参数走临时文件,规避 ARG_MAX 限制 *  - 递归参数过滤,防命令注入 *  - exec + PID 双重确认子进程启动 *  - 日志文件自动轮转(50MB 归档) *  - 重试指数退避(由 handler 执行) */classAsyncTask{    // -----------------------------------------------------------------------    // 可配置项(通过 setter 修改,不要直接改这里)    // -----------------------------------------------------------------------    /** 日志根目录(建议放 web 不可访问的路径) */    private static string $logDir '';    /** 队列文件路径(JSONL 格式,每行一个任务) */    private static string $queueFile '';    /** 任务白名单 */    private static array $taskWhitelist = [        'sendSms',        'sendEmail',        'generateExcel',        'backupDb',    ];    /** 同一任务名允许同时运行的最大子进程数,超限自动入队 */    private static int $maxConcurrent 10;    /** 单次 processQueue() 最多消费的条目数(防止一次跑太久) */    private static int $queueBatchSize 20;    /** 日志文件轮转阈值(字节),默认 50MB */    private static int $logRotateSize 52428800;    /** boot 标志 */    private static bool $booted false;    // -----------------------------------------------------------------------    // 公开配置接口    // -----------------------------------------------------------------------    public static functionsetWhitelist(array$tasks): void    {        self::$taskWhitelist array_values(            array_unique(array_merge(self::$taskWhitelist$tasks))        );    }    public static functionsetLogDir(string$dir): void    {        self::$logDir rtrim($dir'/\\');    }    public static functionsetQueueFile(string$path): void    {        self::$queueFile $path;    }    public static functionsetMaxConcurrent(int$max): void    {        self::$maxConcurrent max(1$max);    }    public static functionsetQueueBatchSize(int$size): void    {        self::$queueBatchSize max(1$size);    }    public static functionsetLogRotateSizeMB(int$mb): void    {        self::$logRotateSize $mb 1024 * 1024;    }    // -----------------------------------------------------------------------    // 核心入口    // -----------------------------------------------------------------------    /**     * 提交异步任务     *     * 流程:     *   白名单校验 → 并发检测 → [未超限] 直接启动子进程     *                          → [超  限] 写入文件队列,等待 processQueue() 消费     *     * @param string $taskName 任务名(必须在白名单内)     * @param array  $params   任务参数(任意深度嵌套均可)     * @param int    $retry    最多执行次数(含首次)     *     * @return bool true = 已启动 或 已入队;false = 被拒绝(白名单不通过)     */    public static functionrun(string$taskNamearray$params = [], int$retry 3): bool    {        self::boot();        // 1. 白名单校验        if(!in_array($taskNameself::$taskWhitelisttrue)) {            self::writeLog(                self::masterLogFile(),                'ERROR',                "非白名单任务被拒绝",                ['task' => $taskName]            );            return false;        }        // 2. 递归过滤参数        $params self::filterParams($params);        // 3. 并发检测(仅 Unix)        $overLimit false;        $running   = 0;        if(self::isUnix()) {            exec('pgrep -c -f ' . escapeshellarg($taskName), $cntOut);            $running   = (int)trim($cntOut[0] ?? '0');            $overLimit $running >= self::$maxConcurrent;        }        if($overLimit) {            // 并发已满 → 入队,不丢任务            $queued self::enqueue($taskName$params$retry);            self::writeLog(                self::masterLogFile(),                $queued 'INFO''ERROR',                $queued                    ? "并发已满,任务已入队等待消费"                    : "并发已满,且入队失败!任务已丢失!",                ['task' => $taskName'running' => $running'limit' => self::$maxConcurrent]            );            return $queued;        }        // 4. 并发未满 → 直接启动子进程        return self::dispatch($taskName$params$retry);    }    /**     * 消费队列(将积压的任务取出并依次启动子进程)     *     * 调用时机(三选一,按场景选择):     *   A. 低流量:在每次 run() 之后顺带调用     *      AsyncTask::run('sendSms', $p);     *      AsyncTask::processQueue();     *     *   B. 推荐:crontab 每分钟执行一次消费脚本     *      * * * * * php /path/to/queue_worker.php >> /path/async_logs/worker.log 2>&1     *     *   C. 高流量:Supervisor 持续运行消费脚本     *     * @return int 本次实际消费(成功启动)的任务数     */    public static functionprocessQueue(): int    {        self::boot();        $queueFile self::$queueFile;        if(!file_exists($queueFile) || filesize($queueFile) === 0) {            return 0;        }        // 独占锁:读取全部内容 + 截断(原子操作,防并发重复消费)        $fp fopen($queueFile'c+');        if(!$fp || !flock($fp, LOCK_EX)) {            self::writeLog(self::masterLogFile(), 'WARN'"队列文件加锁失败,跳过本次消费");            return 0;        }        $content stream_get_contents($fp);        $lines   = array_values(array_filter(array_map('trim'explode("\n"$content))));        if(empty($lines)) {            flock($fp, LOCK_UN);            fclose($fp);            return 0;        }        // 切分:本批处理 + 剩余回写        $batch     = array_splice($lines0self::$queueBatchSize);        $remaining $lines;        ftruncate($fp0);        rewind($fp);        if(!empty($remaining)) {            fwrite($fpimplode("\n"$remaining) . "\n");        }        flock($fp, LOCK_UN);        fclose($fp);        // 逐条启动子进程        $dispatched 0;        foreach($batch as $line) {            $item json_decode($linetrue);            if(                !is_array($item)                || empty($item['task'])                || !isset($item['params'], $item['retry'])            ) {                self::writeLog(                    self::masterLogFile(),                    'WARN',                    "队列条目格式错误,已跳过",                    ['raw' => mb_substr($line0200)] // 避免超长日志                );                continue;            }            $ok self::dispatch($item['task'], $item['params'], (int)$item['retry']);            self::writeLog(                self::masterLogFile(),                $ok 'INFO''ERROR',                $ok "队列任务消费成功""队列任务消费失败",                ['task' => $item['task'], 'queued_at' => $item['queued_at'] ?? '-']            );            if($ok) {                $dispatched++;            }        }        self::writeLog(            self::masterLogFile(),            'INFO',            "队列消费批次结束",            ['dispatched' => $dispatched'remaining' => count($remaining)]        );        return $dispatched;    }    /**     * 查询当前队列积压数量(不加锁,仅供监控参考)     */    public static functionqueueSize(): int    {        self::boot();        if(!file_exists(self::$queueFile)) {            return 0;        }        $content file_get_contents(self::$queueFile);        return count(array_filter(array_map('trim'explode("\n"$content))));    }    // -----------------------------------------------------------------------    // 内部:入队    // -----------------------------------------------------------------------    private static functionenqueue(string$taskNamearray$paramsint$retry): bool    {        $item json_encode([            'task'      => $taskName,            'params'    => $params,            'retry'     => $retry,            'queued_at' => date('Y-m-d H:i:s'),        ], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);        $written file_put_contents(self::$queueFile$item "\n", FILE_APPEND | LOCK_EX);        return $written !== false;    }    // -----------------------------------------------------------------------    // 内部:启动子进程    // -----------------------------------------------------------------------    private static functiondispatch(string$taskNamearray$paramsint$retry): bool    {        // 参数写临时文件(规避 ARG_MAX ~2MB 限制)        $tmpFile self::writeTmpParams($params);        if($tmpFile === null) {            self::writeLog(                self::masterLogFile(),                'ERROR',                "临时参数文件写入失败",                ['task' => $taskName]            );            return false;        }        $phpBin  = escapeshellcmd(PHP_BINARY);        $handler escapeshellarg(self::handlerPath());        $tName   = escapeshellarg($taskName);        $tFile   = escapeshellarg($tmpFile);        $tRetry  = (int)$retry;        $logFile self::taskLogFile($taskName);        if(self::isUnix()) {            $cmd self::buildUnixCmd($phpBin$handler$tName$tFile$tRetry$logFile);        } else {            $cmd "start /B {$phpBin}{$handler}{$tName}{$tFile}{$tRetry} >> {$logFile} 2>&1";        }        exec($cmd);        // PID 确认(Unix 专属)        $launched true;        if(self::isUnix()) {            usleep(500000);            exec('pgrep -n -f ' . escapeshellarg($taskName), $pidOut);            $launched = !empty(trim($pidOut[0] ?? ''));        }        if(!$launched) {            @unlink($tmpFile);            self::writeLog(                self::masterLogFile(),                'ERROR',                "子进程未检测到,启动失败",                ['task' => $taskName'cmd' => $cmd]            );            return false;        }        self::writeLog(            self::masterLogFile(),            'INFO',            "子进程已确认启动",            ['task' => $taskName'tmp' => $tmpFile]        );        return true;    }    // -----------------------------------------------------------------------    // 内部辅助    // -----------------------------------------------------------------------    private static functionboot(): void    {        if(self::$booted) {            return;        }        if(self::$logDir === '') {            self::$logDir __DIR__ . '/async_logs';        }        if(self::$queueFile === '') {            self::$queueFile self::$logDir '/task_queue.jsonl';        }        if(!is_dir(self::$logDir)) {            mkdir(self::$logDir0755true);        }        self::$booted true;    }    private static functionfilterParams(array$params): array    {        array_walk_recursive($paramsstatic function (&$value): void {            if(is_string($value)) {                $value preg_replace('/[&;|`$><\'"\\n\\r]/'''$value);            }        });        return $params;    }    private static functionwriteTmpParams(array$params): ?string    {        $tmpFile sys_get_temp_dir()            . DIRECTORY_SEPARATOR            . 'async_' . bin2hex(random_bytes(8)) . '_' . time() . '.json';        $json    = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);        $written file_put_contents($tmpFile$json, LOCK_EX);        return $written !== false ? $tmpFile null;    }    private static functionwriteLog(        string $logFile,        string $level,        string $msg,        array  $data = []    ): void {        if(file_exists($logFile) && filesize($logFile) >= self::$logRotateSize) {            rename($logFile$logFile '.' . date('His') . '.bak');        }        $line sprintf(            "[%s] [%-5s] %s%s\n",            date('Y-m-d H:i:s'),            strtoupper($level),            $msg,            empty($data) ? '' : ' ' . json_encode($data, JSON_UNESCAPED_UNICODE)        );        file_put_contents($logFile$line, FILE_APPEND | LOCK_EX);    }    private static functionmasterLogFile(): string    {        return self::$logDir '/task_master_' . date('Ymd') . '.log';    }    private static functiontaskLogFile(string$taskName): string    {        $safe preg_replace('/[^a-zA-Z0-9_\-]/'''$taskName);        return self::$logDir "/{$safe}_" . date('Ymd') . '.log';    }    private static functionhandlerPath(): string    {        return __DIR__ . '/async_task_handler.php';    }    private static functionisUnix(): bool    {        return DIRECTORY_SEPARATOR === '/';    }    /**     * 构建 Unix 下彻底脱离父进程的启动命令(避免僵尸进程)     *     * 僵尸进程产生的根本原因:     *   父进程 fork 出子进程,子进程退出后内核保留退出状态(变为僵尸),     *   直到父进程调用 wait() 读取状态后才真正销毁。     *   PHP-FPM/Apache 作为父进程,并不会主动 wait() 它 exec() 出来的孙进程。     *     * 解决思路(三层保险):     *     *   1. setsid:创建新的 session + 进程组,彻底脱离 PHP-FPM 的进程组。     *              即使 FPM reload/重启,SIGHUP 也不会传播到子进程。     *     *   2. 双重 fork(sh -c '...' &):     *              sh 本身是第一层 fork,& 让 sh 再 fork 一个孙进程后立刻退出。     *              孙进程成为孤儿,由 init/systemd(PID 1)收养。     *              init 始终调用 wait(),孤儿退出后立即被回收,永远不会变成僵尸。     *     *   3. nohup:防止子进程因 SIGHUP 被意外终止(双重保险,和 setsid 互补)。     *     * 最终进程树(执行后):     *   PID 1 (init/systemd)     *     └── php async_task_handler.php  ← 孤儿进程,由 init 收养并回收     *     * PHP-FPM Worker 进程树中不再有任何残留子进程。     */    private static functionbuildUnixCmd(        string $phpBin,        string $handler,        string $tName,        string $tFile,        int    $tRetry,        string $logFile    ): string {        // 检测 setsid 是否可用(绝大多数 Linux 发行版均有)        $hasSetsid = !empty(trim(shell_exec('command -v setsid 2>/dev/null') ?? ''));        $innerCmd "{$phpBin}{$handler}{$tName}{$tFile}{$tRetry} >> {$logFile} 2>&1";        if($hasSetsid) {            // 最优方案:setsid 创建新 session + nohup 防 SIGHUP + & 后台运行            // setsid 本身就是 double-fork 的封装,子进程完全独立于父进程组/session            return "setsid nohup {$innerCmd} &";        }        // 降级方案:手动双重 fork(适用于没有 setsid 的极少数环境)        // 外层 sh -c '...' & :第一层 fork,sh 进入后台        // 内层 nohup ... &   :sh 内部再 fork 一次,真正的子进程成为孤儿        // sh 随即退出,孤儿被 init 收养        return "sh -c 'nohup {$innerCmd} &' &";    }}

配套任务处理文件 async_task_handler.php

(和 AsyncTask.php 同目录,子进程执行的核心文件)
<?php/** * 异步任务处理脚本(子进程执行) * * 安全限制:只允许 CLI 模式调用,禁止 web 直接访问。 * 由 AsyncTask::dispatch() 通过 exec() 调用,请勿手动执行。 * * 用法:php async_task_handler.php <taskName> <tmpParamsFile> <maxAttempts> */// -----------------------------------------------------------------------// 0. 安全门:只允许 CLI 执行// -----------------------------------------------------------------------PHP_SAPI === 'cli' or exit('Access denied.' . PHP_EOL);// -----------------------------------------------------------------------// 1. 运行时设置// -----------------------------------------------------------------------set_time_limit(0);ini_set('memory_limit''256M');// -----------------------------------------------------------------------// 2. 解析命令行参数// -----------------------------------------------------------------------$argv $GLOBALS['argv'] ?? [];if(count($argv) < 4) {    fwrite(STDERR, "[ERROR] 参数不足。用法: php async_task_handler.php <taskName> <tmpParamsFile> <maxAttempts>\n");    exit(1);}$taskName    = (string)$argv[1];$tmpFile     = (string)$argv[2];$maxAttempts max(1, (int)$argv[3]);// -----------------------------------------------------------------------// 3. 读取 & 立即删除临时参数文件// -----------------------------------------------------------------------if(!file_exists($tmpFile) || !is_readable($tmpFile)) {    fwrite(STDERR, "[ERROR] 临时参数文件不存在或不可读: {$tmpFile}\n");    exit(1);}$rawJson file_get_contents($tmpFile);@unlink($tmpFile); // 读完立即删除,无论后续是否报错$params json_decode($rawJsontrue);unset($rawJson);if(json_last_error() !== JSON_ERROR_NONE) {    fwrite(STDERR, "[ERROR] 参数 JSON 解析失败: " . json_last_error_msg() . "\n");    exit(1);}// -----------------------------------------------------------------------// 4. 日志工具(带文件大小轮转,50MB 归档)// -----------------------------------------------------------------------$logDir      = __DIR__ . '/async_logs';$logRotateB  = 50 * 1024 * 1024;if(!is_dir($logDir)) {    mkdir($logDir0755true);}$safeTaskName preg_replace('/[^a-zA-Z0-9_\-]/'''$taskName);$logFile      = $logDir "/{$safeTaskName}_" . date('Ymd') . '.log';$log static function (string $levelstring $msguse ($logFile, $logRotateB): void {    if (file_exists($logFile) && filesize($logFile) >= $logRotateB) {        rename($logFile, $logFile . '.' . date('His') . '.bak');    }    $line sprintf("[%s] [%-5s] %s\n"date('Y-m-d H:i:s'), strtoupper($level), $msg);    file_put_contents($logFile$line, FILE_APPEND | LOCK_EX);};// -----------------------------------------------------------------------// 5. 任务注册表//    新增任务:在此处添加一个 key => Closure 即可,同时在 AsyncTask::$taskWhitelist 里加上任务名// -----------------------------------------------------------------------$taskHandlers = [    // ------------------------------------------------------------------    // 发送短信    // ------------------------------------------------------------------    'sendSms' => static function (array $params): string {        $mobile $params['mobile'] ?? '';        $code   = $params['code']   ?? '';        if($mobile === '' || $code === '') {            throw new RuntimeException("短信参数缺失:mobile={$mobile}, code={$code}");        }        // 真实业务(取消注释):        // $res = curlPost('https://sms.example.com/send', ['mobile' => $mobile, 'code' => $code]);        // if (empty($res['success'])) {        //     throw new RuntimeException("短信接口返回失败:" . json_encode($res));        // }        sleep(2); // 模拟耗时,上线删除        return "短信发送成功:{$mobile},验证码:{$code}";    },    // ------------------------------------------------------------------    // 发送邮件    // ------------------------------------------------------------------    'sendEmail' => static function (array $params): string {        $email   = $params['email']   ?? '';        $title   = $params['title']   ?? '';        $content $params['content'] ?? '';        if($email === '' || $title === '') {            throw new RuntimeException("邮件参数缺失:email={$email}, title={$title}");        }        // 真实业务(PHPMailer):        // $mailer = new PHPMailer(true);        // $mailer->addAddress($email);        // $mailer->Subject = $title;        // $mailer->Body    = $content;        // $mailer->send();  // 失败会抛 PHPMailer\PHPMailer\Exception        sleep(3);        return "邮件发送成功:{$email},标题:{$title}";    },    // ------------------------------------------------------------------    // 生成 Excel    // ------------------------------------------------------------------    'generateExcel' => static function (array $params): string {        $fileName $params['fileName'] ?? 'export_' . date('YmdHis');        $data     = $params['data']     ?? [];        if(empty($data)) {            throw new RuntimeException("Excel 数据为空");        }        $outDir __DIR__ . '/uploads';        if(!is_dir($outDir)) {            mkdir($outDir0755true);        }        $filePath $outDir "/{$fileName}.xlsx";        // 真实业务(PhpSpreadsheet):        // $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();        // $sheet = $spreadsheet->getActiveSheet();        // foreach ($data as $rowIdx => $row) {        //     $sheet->fromArray($row, null, 'A' . ($rowIdx + 1));        // }        // (new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet))->save($filePath);        sleep(5);        unset($data); // 大数组用完立即释放        return "Excel 生成成功:{$filePath}";    },    // ------------------------------------------------------------------    // 数据库备份    // ------------------------------------------------------------------    'backupDb' => static function (array $params): string {        $dbName   = $params['dbName']   ?? '';        $savePath $params['savePath'] ?? __DIR__ . '/backups';        if($dbName === '') {            throw new RuntimeException("数据库名缺失");        }        if(!is_dir($savePath)) {            mkdir($savePath0755true);        }        $backupFile $savePath "/{$dbName}_" . date('YmdHis') . '.sql';        // 真实业务(mysqldump):        // $cmd = sprintf(        //     'mysqldump -u%s -p%s %s > %s',        //     escapeshellarg(DB_USER),        //     escapeshellarg(DB_PASS),        //     escapeshellarg($dbName),        //     escapeshellarg($backupFile)        // );        // exec($cmd, $out, $exitCode);        // if ($exitCode !== 0) {        //     throw new RuntimeException("mysqldump 退出码:{$exitCode}");        // }        sleep(4);        return "数据库备份成功:{$backupFile}";    },    // ------------------------------------------------------------------    // 扩展示例:推送消息    // 同时需要在 AsyncTask::setWhitelist(['pushMsg']) 里注册    // ------------------------------------------------------------------    // 'pushMsg' => static function (array $params): string {    //     $userId  = $params['userId']  ?? '';    //     $content = $params['content'] ?? '';    //     if ($userId === '' || $content === '') {    //         throw new RuntimeException("推送参数缺失");    //     }    //     // 调用推送 SDK ...    //     return "推送成功:用户 {$userId}";    // },];// -----------------------------------------------------------------------// 6. 校验任务是否在注册表中// -----------------------------------------------------------------------if(!isset($taskHandlers[$taskName])) {    $log('ERROR'"任务未注册:{$taskName}");    exit(1);}$handler $taskHandlers[$taskName];// -----------------------------------------------------------------------// 7. 执行任务(指数退避重试)//    第 1 次失败 → 等 1s → 第 2 次失败 → 等 2s → 第 3 次失败 → 等 4s → …//    最大等待上限 30s,防止进程长期驻留// -----------------------------------------------------------------------$success   = false;$lastError '';for($attempt 1$attempt <= $maxAttempts$attempt++) {    try {        $result  = $handler($params);        $log('INFO'"任务成功(第 {$attempt}/{$maxAttempts} 次):{$result}");        $success true;        break;    } catch(Throwable $e) {        // 捕获 Exception + Error        $lastError $e->getMessage();        $log('WARN'"任务失败(第 {$attempt}/{$maxAttempts} 次):{$lastError}");        if($attempt $maxAttempts) {            $delay min((int)pow(2$attempt 1), 30); // 1,2,4,8,…,30            $log('INFO'"指数退避,等待 {$delay}s 后重试…");            sleep($delay);        }    }}// -----------------------------------------------------------------------// 8. 最终结果// -----------------------------------------------------------------------if(!$success) {    $log('ERROR'"任务彻底失败(共 {$maxAttempts} 次),最后错误:{$lastError}");    exit(1);}exit(0);
重试的队列核心queue_worker.php
<?php/** * 队列消费脚本 * * 推荐用法(crontab,每分钟执行一次): *   * * * * * php /path/to/queue_worker.php >> /path/async_logs/worker.log 2>&1 * * 或通过 Supervisor 持续运行(适合高流量场景): *   [program:async_queue_worker] *   command=php /path/to/queue_worker.php --loop *   autostart=true *   autorestart=true */PHP_SAPI === 'cli' or exit('Access denied.' . PHP_EOL);require_once __DIR__ . '/AsyncTask.php';// 可选:覆盖默认配置// AsyncTask::setLogDir('/var/log/async');// AsyncTask::setQueueFile('/var/log/async/task_queue.jsonl');// AsyncTask::setQueueBatchSize(50);$loop in_array('--loop'$argv ?? [], true);do {    $count AsyncTask::processQueue();    echo sprintf("[%s] 本次消费任务数:%d,队列剩余:%d\n",        date('Y-m-d H:i:s'), $countAsyncTask::queueSize());    if($loop) {        sleep(5); // 持续模式每 5 秒轮询一次    }while($loop);
# 方式 A:crontab,每分钟消费一次(低流量推荐)* * * * * php /path/to/queue_worker.php >> /path/async_logs/worker.log 2>&1# 方式 B:持续轮询(高流量,配合 Supervisor)php queue_worker.php --loop

四、完整使用示例

重点说一下 Demo 3 导出的三个场景

场景 A:小数据量(< 1000 行)

数据直接随参数传入,最简单,直接用。

场景 B:10 万行大数据

数据照样塞进 $params,AsyncTask 内部自动走临时文件绕过命令行长度限制,调用方无感知。但有一个坑要注意:unset($data); // ← 必须加!主线程把 10 万行数组组装完交给子进程后,自己要立刻释放,否则主线程内存同样会爆。

场景 C:分片导出(生产最推荐)

主线程完全不查数据,只负责「算出分片参数 → 提交 N 个任务」,每个 handler 子进程自己去数据库分页查询并生成一个 Excel 文件。好处:

  • 主线程内存接近零占用
  • N 个子进程并发跑,总耗时约等于单片耗时而不是累加
  • 某片失败只重试那一片
<?php/** * AsyncTask 使用示例合集 * * 目录: *   Demo 1 - 用户注册 → 异步发送短信 *   Demo 2 - 异步发送邮件(含 HTML 内容) *   Demo 3 - 大数据导出 Excel(含进度查询接口) *   Demo 4 - 数据库备份(定时任务场景) *   Demo 5 - 自定义任务:推送消息 *   Demo 6 - 队列状态监控 * * 所有 Demo 均模拟「接口调用」场景,run() 调用后立即返回,不阻塞主线程。 */require_once __DIR__ . '/AsyncTask.php';// ============================================================// 全局配置(可选,不调用则使用默认值)// ============================================================AsyncTask::setLogDir(__DIR__ . '/async_logs');       // 日志目录AsyncTask::setQueueFile(__DIR__ . '/async_logs/task_queue.jsonl'); // 队列文件AsyncTask::setMaxConcurrent(10);                     // 同类任务最大并发数AsyncTask::setQueueBatchSize(20);                    // 每次消费队列的批次大小AsyncTask::setLogRotateSizeMB(50);                   // 日志文件超 50MB 自动归档// 运行指定 Demo(命令行传参:php demos.php 1)$demoId = (int)($argv[1] ?? 0);switch($demoId) {    case 1demo1_register();       break;    case 2demo2_sendEmail();      break;    case 3demo3_exportExcel();    break;    case 4demo4_backupDb();       break;    case 5demo5_customTask();     break;    case 6demo6_queueMonitor();   break;    default:        echo "用法:php demos.php <编号>\n";        echo "  1 - 注册发短信\n";        echo "  2 - 发送邮件\n";        echo "  3 - 导出大 Excel\n";        echo "  4 - 数据库备份\n";        echo "  5 - 自定义推送任务\n";        echo "  6 - 队列状态监控\n";}// ============================================================// Demo 1:用户注册 → 异步发送短信// ============================================================functiondemo1_register(): void{    echo "=== Demo 1:用户注册 ===\n";    $startTime microtime(true);    // 模拟注册接口    $result userRegister('13800138000');    $elapsed round((microtime(true) - $startTime) * 10002);    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";    echo "接口耗时:{$elapsed}ms(短信在后台发送,主线程不阻塞)\n";}functionuserRegister(string$mobile): array{    $code rand(10009999);    // --- 同步操作 ---    // saveUserToDb($mobile, $code);           // 写数据库    // saveVerifyCode($mobile, $code, 300);    // 写验证码缓存(5分钟有效)    // --- 异步操作:提交后立即返回,不等短信发出 ---    $submitted AsyncTask::run('sendSms', [        'mobile' => $mobile,        'code'   => (string)$code,    ], retry: 3);    return [        'code'      => 200,        'msg'       => '注册成功,验证码已发送',        'mobile'    => $mobile,        'async_sms' => $submitted '已提交' : '提交失败,请稍后重试',    ];}// ============================================================// Demo 2:发送邮件(含 HTML 正文)// ============================================================functiondemo2_sendEmail(): void{    echo "=== Demo 2:异步发送邮件 ===\n";    $startTime microtime(true);    // 模拟订单完成后发送确认邮件    $result sendOrderConfirmEmail(        orderId'ORD20240315001',        email:   'user@example.com',        name:    '张三',        amount:  '¥299.00'    );    $elapsed round((microtime(true) - $startTime) * 10002);    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";    echo "接口耗时:{$elapsed}ms\n";}functionsendOrderConfirmEmail(    string $orderId,    string $email,    string $name,    string $amount): array {    // HTML 邮件正文(真实场景可渲染模板文件)    $htmlContent <<<HTML        <h2>亲爱的 {$name},您的订单已完成!</h2>        <p>订单号:<strong>{$orderId}</strong></p>        <p>实付金额:<strong>{$amount}</strong></p>        <p>感谢您的购买,如有疑问请联系客服。</p>    HTML;    $submitted AsyncTask::run('sendEmail', [        'email'   => $email,        'title'   => "订单确认 - {$orderId}",        'content' => $htmlContent,    ], retry2);    return [        'code'        => 200,        'msg'         => '订单处理完成',        'order_id'    => $orderId,        'async_email' => $submitted '确认邮件已提交发送' : '邮件提交失败',    ];}// ============================================================// Demo 3:大数据导出 Excel(核心 Demo)// ============================================================functiondemo3_exportExcel(): void{    echo "=== Demo 3:大数据导出 Excel ===\n\n";    // --- 场景 A:小数据量(< 1000 行),直接传参数 ---    echo "--- 场景 A:小数据量直接导出 ---\n";    $startTime microtime(true);    $result exportSmallData();    $elapsed round((microtime(true) - $startTime) * 10002);    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";    echo "接口耗时:{$elapsed}ms\n\n";    // --- 场景 B:大数据量(10 万行),参数走临时文件(自动处理,无需手动干预) ---    echo "--- 场景 B:10 万行大数据导出 ---\n";    $startTime microtime(true);    $result exportBigData();    $elapsed round((microtime(true) - $startTime) * 10002);    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";    echo "接口耗时:{$elapsed}ms(10 万行数据后台生成,接口瞬间返回)\n\n";    // --- 场景 C:超大数据量分片导出(推荐:避免单次内存爆炸)---    echo "--- 场景 C:分片导出(每片 1 万行,共 10 片)---\n";    $startTime microtime(true);    $result exportByChunks(totalRows100000, chunkSize10000);    $elapsed round((microtime(true) - $startTime) * 10002);    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";    echo "接口耗时:{$elapsed}ms(10 个子任务并发后台跑,主线程零等待)\n";}/** 场景 A:小数据量,数据直接随参数传入 */functionexportSmallData(): array{    // 模拟从数据库查出 200 行    $data = [];    for($i 1$i <= 200$i++) {        $data[] = [            'id'       => $i,            'name'     => "商品{$i}",            'price'    => rand(10500),            'stock'    => rand(01000),            'category' => ['电子''服装''食品''家居'][rand(03)],        ];    }    $fileName  'goods_small_' . date('YmdHis');    $submitted AsyncTask::run('generateExcel', [        'fileName' => $fileName,        'data'     => $data,        // 可选:告诉 handler 生成完后通知谁        'notify'   => ['type' => 'callback''url' => 'https://example.com/excel/done'],    ]);    return [        'code'      => 200,        'msg'       => 'Excel 正在后台生成',        'file_name' => $fileName '.xlsx',        'async'     => $submitted '已提交' : '提交失败',        'tip'       => '生成完成后文件将保存至 uploads/ 目录',    ];}/** 场景 B:10 万行大数据,数据直接传入(AsyncTask 内部自动走临时文件,规避 ARG_MAX) */functionexportBigData(): array{    // 实际项目中应从数据库分批查询,此处用循环模拟    $data = [];    for($i 1$i <= 100000$i++) {        $data[] = [            'order_id'    => 'ORD' . str_pad((string)$i8'0', STR_PAD_LEFT),            'user_id'     => rand(10009999),            'product'     => "商品{$i}",            'qty'         => rand(110),            'unit_price'  => rand(101000),            'total'       => rand(1010000),            'status'      => ['待支付''已支付''已发货''已完成'][rand(03)],            'created_at'  => date('Y-m-d H:i:s'time() - rand(086400 * 30)),        ];    }    $fileName  'orders_full_' . date('YmdHis');    $submitted AsyncTask::run('generateExcel', [        'fileName' => $fileName,        'data'     => $data,   // 10 万行数据由 AsyncTask 自动写临时文件传递,不会撑爆命令行    ]);    unset($data); // 主进程用完立即释放,不要等 GC    return [        'code'      => 200,        'msg'       => 'Excel 正在后台生成,请稍候下载',        'file_name' => $fileName '.xlsx',        'async'     => $submitted '已提交' : '提交失败',        'tip'       => '数据量较大,预计 30~60 秒完成',    ];}/** * 场景 C:超大数据分片导出 * * 思路:主线程只负责「分片 + 提交任务」,每片单独生成一个 Excel 文件, * 最终可由另一个异步任务将多个文件合并,或直接打包成 zip 提供下载。 * * 优点: *   - 主线程不持有大数组,内存极低 *   - 各片并发执行,总耗时约等于单片耗时 *   - 某片失败只需重试该片,不影响其他 */functionexportByChunks(int$totalRowsint$chunkSize): array{    $totalChunks = (int)ceil($totalRows $chunkSize);    $batchId     = 'batch_' . date('YmdHis') . '_' . rand(10009999);    $submitted   = 0;    $failed      = 0;    $fileNames   = [];    for($chunk 0$chunk $totalChunks$chunk++) {        $offset   = $chunk $chunkSize;        $limit    = $chunkSize;        $fileName "{$batchId}_part" . str_pad((string)($chunk 1), 3'0', STR_PAD_LEFT);        // 真实项目:这里只传查询参数,让 handler 自己去数据库分页查        // 避免主线程查出全量数据再传参(主线程内存爆炸的根源)        $ok AsyncTask::run('generateExcel', [            'fileName' => $fileName,            'source'   => [            // 告诉 handler 去哪查数据                'type'   => 'db_query',                'sql'    => "SELECT * FROM orders ORDER BY id LIMIT {$limit} OFFSET {$offset}",                // 真实场景建议传结构化参数,handler 内拼 SQL,避免 SQL 注入                // 'table'  => 'orders',                // 'offset' => $offset,                // 'limit'  => $limit,            ],        ]);        if($ok) {            $submitted++;            $fileNames[] = $fileName '.xlsx';        } else {            $failed++;        }    }    return [        'code'        => 200,        'msg'         => "分片任务已提交",        'batch_id'    => $batchId,        'total_chunks' => $totalChunks,        'submitted'   => $submitted,        'failed'      => $failed,        'files'       => $fileNames,        'tip'         => '所有分片完成后,可调用合并接口将文件打包成 zip',    ];}// ============================================================// Demo 4:数据库备份(定时任务场景)// ============================================================functiondemo4_backupDb(): void{    echo "=== Demo 4:异步数据库备份 ===\n";    // 同时备份多个库(每个库独立子进程,互不影响)    $databases = [        ['name' => 'shop_db',    'path' => '/www/backups/shop'],        ['name' => 'user_db',    'path' => '/www/backups/user'],        ['name' => 'finance_db''path' => '/www/backups/finance'],    ];    $results = [];    foreach($databases as $db) {        $ok AsyncTask::run('backupDb', [            'dbName'   => $db['name'],            'savePath' => $db['path'],        ], retry2);        $results[] = [            'db'        => $db['name'],            'submitted' => $ok,            'status'    => $ok '备份任务已提交' : '提交失败',        ];    }    echo json_encode([        'code'    => 200,        'msg'     => '数据库备份任务已全部提交',        'details' => $results,    ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";    echo "\n建议在 crontab 中配置(每天凌晨 2 点执行):\n";    echo "0 2 * * * php " . __FILE__ . " 4 >> /var/log/backup_cron.log 2>&1\n";}// ============================================================// Demo 5:自定义任务(以「推送消息」为例)// ============================================================functiondemo5_customTask(): void{    echo "=== Demo 5:自定义任务 - 推送消息 ===\n\n";    // Step 1:将自定义任务加入白名单    AsyncTask::setWhitelist(['pushMsg']);    // Step 2:在 async_task_handler.php 的 $taskHandlers 数组中添加对应处理函数:    //    // 'pushMsg' => static function (array $params): string {    //     $userId  = $params['userId']  ?? '';    //     $title   = $params['title']   ?? '';    //     $content = $params['content'] ?? '';    //     $channel = $params['channel'] ?? 'app'; // app / sms / email    //    //     if ($userId === '' || $content === '') {    //         throw new RuntimeException("推送参数缺失");    //     }    //    //     // 调用推送 SDK(极光/个推/自建)    //     // $push = new JiGuangPush();    //     // $push->toUser($userId)->send($title, $content);    //    //     sleep(1);    //     return "推送成功:用户 {$userId},渠道:{$channel}";    // },    // Step 3:调用    $userIds = [10011002100310041005];    $success 0;    foreach($userIds as $uid) {        $ok AsyncTask::run('pushMsg', [            'userId'  => $uid,            'title'   => '您有新订单待处理',            'content' => '您的订单 ORD20240315001 已发货,请注意查收!',            'channel' => 'app',        ]);        if($ok$success++;    }    echo "批量推送结果:{$success}/" . count($userIds) . " 个任务提交成功\n";    echo "提示:pushMsg 已加入白名单,但 handler 中对应函数需手动添加(见注释)\n";}// ============================================================// Demo 6:队列状态监控// ============================================================functiondemo6_queueMonitor(): void{    echo "=== Demo 6:队列状态监控 ===\n\n";    // 模拟高并发:快速提交 30 个任务,触发部分入队    echo "模拟提交 30 个短信任务(maxConcurrent=3,其余自动入队)...\n";    AsyncTask::setMaxConcurrent(3); // 调低并发限制,方便演示入队效果    $submitted 0;    $queued    = 0;    for($i 1$i <= 30$i++) {        $ok AsyncTask::run('sendSms', [            'mobile' => '138' . str_pad((string)$i8'0', STR_PAD_LEFT),            'code'   => (string)rand(10009999),        ]);        $ok $submitted++ : $queued++;    }    // 实际上 run() 返回 true 包含「直接启动」和「成功入队」两种情况    // 这里只是演示计数,真实监控看下面的 queueSize()    echo "\n当前队列积压数:" . AsyncTask::queueSize() . " 个任务\n\n";    // 手动触发消费(正常由 queue_worker.php 负责)    echo "手动触发队列消费...\n";    $consumed AsyncTask::processQueue();    echo "本次消费:{$consumed} 个任务\n";    echo "消费后队列剩余:" . AsyncTask::queueSize() . " 个任务\n\n";    echo "提示:生产环境建议配置 crontab 自动消费:\n";    echo "* * * * * php " . __DIR__ . "/queue_worker.php >> " . __DIR__ . "/async_logs/worker.log 2>&1\n";}

五、生产级配置(必看)

1. 环境准备

  • 开启 PHP exec 函数:修改 php.ini,确保 disable_functions 中没有 exec
  • 权限配置:async_logsuploadsbackups 目录赋予 0755 权限;
  • 绝对路径:所有文件路径必须用绝对路径(子进程环境无相对路径)。

2. 安全加固

  • 白名单严格控制:只允许执行业务需要的任务,禁止新增未知任务;
  • 参数过滤:封装类已过滤危险字符,禁止传递 rm、cd、mv 等命令参数;
  • 日志权限:日志目录禁止 web 访问(可放 public 目录外);
  • 禁止直接访问:async_task_handler.php 可增加访问限制(比如只允许 cli 执行)。

3. 高并发优化

  • 控制子进程数量:避免短时间提交大量任务,导致服务器过载;
  • 任务拆分:超大任务拆分成多个小任务,分批执行;
  • 资源释放:任务执行完成后,及时 unset 大数组、关闭文件句柄。

4. 部署方式

  • Linux 服务器
    直接使用,建议配合 Supervisor 监控日志;
  • 虚拟主机
    确认主机允许 exec 函数(部分虚拟主机禁用,可联系服务商开启);
  • Windows 服务器
    将 exec 命令改为 start /B php ...(适配 Windows 后台执行)。

六、常见问题解决

1. 任务提交成功但不执行

  • 检查 async_task_handler.php 路径是否为绝对路径;
  • 检查 PHP 执行权限:which php 确认路径,替换封装类中的 PHP_BINARY
  • 查看日志文件:async_logs 目录下的 error 日志,排查具体错误。

2. 子进程卡死

  • 设置任务超时:在 async_task_handler.php 中为每个任务加超时逻辑;
  • 限制内存:ini_set('memory_limit', '256M'),避免内存溢出;
  • 定期清理:定时清理日志文件、临时文件,避免磁盘占满。

3. 虚拟主机禁用 exec

  • 替代方案:使用 file_put_contents 写入任务队列,单独写一个脚本定时读取执行;
  • 联系服务商:申请开启 exec 函数(仅允许执行 php 命令)。

七、核心优化总结

  1. 无依赖
    不用 Redis/MQ,原生 PHP 就能跑,小项目首选;
  2. 安全
    白名单+参数过滤,防止 exec 注入,生产级加固;
  3. 高效
    子进程后台运行,主线程瞬间返回,解决接口阻塞;
  4. 易扩展
    新增任务只需加白名单+处理函数,不用改核心逻辑;
  5. 可监控
    完整日志记录,任务执行状态、失败原因一目了然。

关注我,后续我会继续分享:PHP 开发规范、框架实战、架构思路、运维技巧、效率工具。关注我,每天 3 分钟,提升开发效率。

欢迎在留言区告诉我:你项目中哪些场景需要异步执行?有没有遇到过子进程执行失败的问题?我们一起交流解决方案!

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-03-27 09:48:16 HTTP/2.0 GET : https://f.mffb.com.cn/a/480334.html
  2. 运行时间 : 0.091758s [ 吞吐率:10.90req/s ] 内存消耗:4,735.76kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=33e52d22cfaa78121d83a918b3e067fb
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000428s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000543s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000251s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000302s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000489s ]
  6. SELECT * FROM `set` [ RunTime:0.000207s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000617s ]
  8. SELECT * FROM `article` WHERE `id` = 480334 LIMIT 1 [ RunTime:0.003600s ]
  9. UPDATE `article` SET `lasttime` = 1774576096 WHERE `id` = 480334 [ RunTime:0.002269s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000410s ]
  11. SELECT * FROM `article` WHERE `id` < 480334 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000629s ]
  12. SELECT * FROM `article` WHERE `id` > 480334 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000602s ]
  13. SELECT * FROM `article` WHERE `id` < 480334 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002058s ]
  14. SELECT * FROM `article` WHERE `id` < 480334 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.005709s ]
  15. SELECT * FROM `article` WHERE `id` < 480334 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.004968s ]
0.093397s