PHP 單線程同步執行,耗時操作(發郵件、推送、視頻轉碼、生成報表)會阻塞用戶請求,導致響應緩慢、用戶體驗差、併發能力下降。異步處理將耗時任務放入後台,主流程秒級返回。
使用 fsockopen() 發起 HTTP 請求到自身服務器的另一腳本,發送請求頭後立即關閉連接,不讀取響應,實現「發後即忘」。
/**
* 異步觸發 URL(非阻塞)
* @param string $url 完整 URL(含協議、域名、路徑、查詢參數)
* @param int $timeout 連接超時秒數
* @return bool
*/
function asyncTrigger($url, $timeout = 5) {
$parsed = parse_url($url);
if (!$parsed || !isset($parsed['host'], $parsed['path'])) return false;
$host = $parsed['host'];
$port = $parsed['port'] ?? ($parsed['scheme'] === 'https' ? 443 : 80);
$path = $parsed['path'] . ($parsed['query'] ? '?' . $parsed['query'] : '');
$fp = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$fp) {
error_log("Async trigger failed: $errstr ($errno) for $url");
return false;
}
// 構造 HTTP/1.1 請求頭(Connection: close 關鍵)
$request = "GET $path HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "Connection: close\r\n\r\n";
fwrite($fp, $request);
fclose($fp); // 立即關閉,不等待響應
return true;
}
<?php
require_once 'db.php';
require_once 'async.php'; // 包含上述 asyncTrigger()
$title = $_POST['title'];
$content = $_POST['content'];
$userId = $_SESSION['user_id'];
// 1. 保存文章(核心業務,必須成功)
$articleId = saveArticle($title, $content, $userId);
if (!$articleId) {
die('保存失敗');
}
// 2. 異步觸發郵件發送任務
$mailUrl = "https://{$_SERVER['HTTP_HOST']}/send_subscribe_mail.php?article_id=$articleId";
asyncTrigger($mailUrl);
// 3. 立即響應用戶
echo json_encode(['code' => 0, 'msg' => '發布成功', 'id' => $articleId]);
?>
<?php
ignore_user_abort(true); // 客戶端斷開也繼續執行
set_time_limit(0); // 無時間限制
$articleId = (int)($_GET['article_id'] ?? 0);
if (!$articleId) exit;
// 1. 獲取訂閱用戶列表
$subscribers = getSubscribersByArticle($articleId);
// 2. 逐個發送(可再細分為隊列+Worker)
foreach ($subscribers as $user) {
$success = sendMail($user['email'], '新文章發布', "標題:$title\n內容摘要...");
logMailResult($articleId, $user['id'], $success);
usleep(100000); // 0.1s 節流,避免觸發郵件服務商限流
}
// 3. 記錄完成
updateArticleMailStatus($articleId, 'completed');
?>
| 問題 | 解決方案 |
|---|---|
| 大量併發導致端口耗盡 | 使用持久連接池,或改用消息隊列 |
| HTTPS 環境 fsockopen 需 ssl:// 前綴 | fsockopen('ssl://' . $host, 443, ...) |
| 任務丟失、重複執行、無狀態追蹤 | 引入 Redis/RabbitMQ 隊列 + 任務 ID 去重 + 狀態機 |
| 同域名並發限制(瀏覽器/服務器) | 異步端點使用獨立子域名或 IP |
| 安全風險(任意 URL 觸發) | 簽名驗證、內網 IP 白名單、JWT Token |
exec('php task.php > /dev/null 2>&1 &')(僅 CLI,需權限控制)。<?php
// 僅適用於 PHP-FPM (Nginx/Apache mod_fcgid)
echo json_encode(['code' => 0, 'msg' => '發布成功']);
header('Content-Length: ' . ob_get_length());
fastcgi_finish_request(); // 關鍵:刷新輸出並關閉客戶端連接
// 此後代碼在後台繼續執行,用戶已收到響應
sendSubscribeMails($articleId);
?>
fsockopen 自請求是零依賴、快速落地的異步方案,適合中小型項目。隨業務增長,建議遷移至 消息隊列 + Worker 或 Swoole 架構,獲得可靠性、可觀測性與擴展性。