首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Symfony HTTP Cache 深度解析:Gateway Cache、過期驗證模型與 ESI 片段快取

     編輯:PHP 相關知識

為什麼需要 HTTP 快取?

動態 Web 應用每次請求都需執行 PHP 代碼、查詢資料庫、渲染模板。隨著流量增長,伺服器負載成為瓶頸。HTTP 快取的核心目標:讓相同請求的處理邏輯只執行一次,後續請求直接由快取層回應,繞過應用程式。

Symfony 快取架構:站在巨人的肩膀上

Symfony 不重造輪子,而是完全遵循 HTTP 標準 (RFC 7234) 實作快取。四大核心概念:

  1. Gateway Cache (反向代理):位於客戶端與應用之間,快取應用回應,後續請求直接由代理回應。
  2. HTTP 快取標頭:Cache-Control、Expires、ETag、Last-Modified,規範快取行為。
  3. 過期與驗證模型:決定快取內容是否「新鮮」的兩種機制。
  4. ESI (Edge Side Includes):頁面片段級快取,不同片段可有不同快取策略。

快取類型對比

類型位置用途
瀏覽器快取客戶端回退導航、靜態資源
Proxy Cache網路中間層 (ISP/企業)共享快取,減少頻寬
Gateway Cache伺服器端 (反向代理)提升網站擴展性、可靠性、效能

Symfony 內建反向代理 (AppCache)

啟用方式

// web/app.php
require_once __DIR__.'/../app/bootstrap.php.cache';
require_once __DIR__.'/../app/AppKernel.php';
require_once __DIR__.'/../app/AppCache.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);
$kernel->loadClassCache();
// 使用 AppCache 包裝預設 Kernel
$kernel = new AppCache($kernel);
$kernel->handle(Request::createFromGlobals())->send();

配置選項 (AppCache.php)

// app/AppCache.php
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;

class AppCache extends HttpCache {
    protected function getOptions() {
        return [
            'debug' => false,                    // 除錯模式 (自動同步 Kernel debug)
            'default_ttl' => 0,                  // 無明確快取資訊時的預設 TTL (秒)
            'private_headers' => ['Authorization', 'Cookie'], // 觸發 private 快取的請求標頭
            'allow_reload' => false,             // 允許客戶端用 no-cache 強制重載
            'allow_revalidate' => false,         // 允許客戶端用 max-age=0 強制重驗證
            'stale_while_revalidate' => 2,       // 背景重驗證時可提供過期內容的秒數 (RFC 5861)
            'stale_if_error' => 60,              // 後端錯誤時可提供過期內容的秒數 (RFC 5861)
        ];
    }
}

注意:PHP 實作的反向代理效能不如 Varnish/Squid (C 語言)。開發/共享主機適用;生產環境高流量建議換用 Varnish,無需修改應用代碼。

HTTP 快取標頭詳解

Cache-Control:萬用標頭

$response = new Response();
// 公共/私有快取
$response->setPublic();   // 可被共享快取 (Gateway/Proxy) 儲存
$response->setPrivate();  // 僅限單一用戶 (瀏覽器) 快取,預設值

// 過期時間
$response->setMaxAge(600);        // 所有快取 600 秒新鮮
$response->setSharedMaxAge(600);  // 僅共享快取 600 秒新鮮 (s-maxage)

// 自定義指令
$response->headers->addCacheControlDirective('must-revalidate', true);

關鍵差異:public 允許 Gateway/Proxy 快取;private 僅限瀏覽器。若響應包含用戶專屬資料 (購物車、個人資料),必須設為 private,否則會洩露給其他用戶。

安全方法限制

HTTP 僅對「安全方法」 (GET、HEAD) 快取。絕不可快取 PUT/POST/DELETE,這些方法會改變伺服器狀態。應用程式處理 GET/HEAD 時不得有副作用。

Symfony 預設快取策略

  • 無任何快取標頭 → Cache-Control: no-cache (不快取)
  • 有其他快取標頭但無 Cache-Control → private, must-revalidate
  • 有 Cache-Control 指令但無 public/private → 自動補 private (除非設定 s-maxage)

過期模型 vs 驗證模型

過期模型:簡單高效

透過 Expires 或 Cache-Control: max-age/s-maxage 告訴快取:「這段內容在 N 秒內都是新鮮的,直接拿去用,別來問我」。

// Expires 標頭 (HTTP/1.0 相容)
$date = new DateTime();
$date->modify('+600 seconds');
$response->setExpires($date); // 自動轉 GMT

// Cache-Control max-age (推薦,HTTP/1.1)
$response->setMaxAge(600);
$response->setSharedMaxAge(600);

限制:Expires 依賴伺服器/客戶端時鐘同步,最多設定 1 年;max-age 以秒為單位,無時鐘偏差問題。

驗證模型:內容變更即時生效

適用於頻繁變動的動態內容。快取儲存響應,但每次請求都詢問應用:「我手上的版本還有效嗎?」應用回應 304 Not Modified (無內容) 或 200 OK (新內容)。

ETag:實體標籤

public function indexAction() {
    $response = $this->render('MyBundle:Main:index.html.twig');
    // 以內容 MD5 作為 ETag (弱驗證傳 true)
    $response->setETag(md5($response->getContent()));
    // 自動比對 Request 的 If-None-Match,匹配則設 304
    $response->isNotModified($this->getRequest());
    return $response;
}

缺點:需先產生完整響應才能計算 ETag,省頻寬但不省 CPU。

Last-Modified:最後修改時間

public function showAction($articleSlug) {
    // 取文章與作者最新更新時間
    $date = max($article->getUpdatedAt(), $author->getUpdatedAt());
    $response->setLastModified($date);
    $response->isNotModified($this->getRequest());
    return $response;
}

比對 Request 的 If-Modified-Since 標頭。

驗證模型效能優化模式

public function showAction($articleSlug) {
    // 1. 只查詢計算 ETag/Last-Modified 所需的最小資料
    $article = $this->getDoctrine()->getRepository(Article::class)->find($articleSlug);
    
    $response = new Response();
    $response->setETag($article->computeETag());
    $response->setLastModified($article->getPublishedAt());
    
    // 2. 未修改直接回 304,避免後續昂貴操作
    if ($response->isNotModified($this->getRequest())) {
        return $response; // 304 無內容,極輕量
    }
    
    // 3. 已修改才執行完整邏輯
    $comments = $this->getDoctrine()->getRepository(Comment::class)->findBy(['article' => $article]);
    return $this->render('article.html.twig', ['article' => $article, 'comments' => $comments], $response);
}

isNotModified() 自動處理:設 304、清空內容、移除無關標頭。

Vary 標頭:同一 URL 的多種變體

當同一 URL 需根據請求標頭 (如 Accept-Encoding、User-Agent) 返回不同內容時:

$response->setVary('Accept-Encoding');
$response->setVary(['Accept-Encoding', 'User-Agent']);
// 產生: Vary: Accept-Encoding, User-Agent

快取將以 URL + Vary 標頭值作為快取鍵,分別儲存壓縮/非壓縮版本等。

過期與驗證結合使用

同時設定 max-age (過期) 與 ETag/Last-Modified (驗證):「在過期前直接用快取;過期後用驗證機制檢查是否需更新」。過期優先級高於驗證。

ESI (Edge Side Includes):片段級快取

啟用 ESI

# config.yml
framework:
    esi: { enabled: true }

控制器與模板

// 主頁面:快取 10 分鐘
public function indexAction() {
    $response = $this->render('index.html.twig');
    $response->setSharedMaxAge(600);
    return $response;
}

// 新聞區塊:獨立快取 1 分鐘
public function newsAction() {
    $response = $this->render('news.html.twig');
    $response->setSharedMaxAge(60);
    return $response;
}
{# index.html.twig #}
<html><body>
主頁內容...
{% render 'AppBundle:News:news' with {}, {'standalone': true} %}
...更多內容
</body></html>

standalone: true 關鍵作用:

  • 有支援 ESI 的 Gateway Cache → 產生 <esi:include src="/_internal/..." />,Gateway 獨立請求片段並組裝
  • 無 ESI 支援 → 直接在 PHP 層渲染合併 (向下相容)

內部路由配置 (必要)

# routing.yml
_internal:
    resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
    prefix: /_internal

片段 Action 必須可透過 URL 存取,內部路由提供此機制。生產環境建議用防火牆限制只允許反向代理 IP 存取。

ESI 進階選項

  • alt:來源失效時的備用 URL
  • ignore_errors: true:片段失敗時靜默移除,不中斷主頁

重要:ESI 片段必須用 s-maxage 而非 max-age,否則瀏覽器會快取整頁合併結果。

快取失效:不需要手動清除

HTTP 模型已內建失效機制:

  • 驗證模型:無需失效,每次請求自動驗證
  • 過期模型:設定合理的過期時間,時間到自然失效

若必須主動清除 (如發佈新文章立即生效),標準方式是發送 PURGE 請求。Symfony 反向代理支援範例:

// AppCache.php
protected function invalidate(Request $request) {
    if ('PURGE' !== $request->getMethod()) {
        return parent::invalidate($request);
    }
    $response = new Response();
    if (!$this->getStore()->purge($request->getUri())) {
        $response->setStatusCode(404, 'Not purged');
    } else {
        $response->setStatusCode(200, 'Purged');
    }
    return $response;
}

務必保護 PURGE 端點,防止惡意清除快取。

總結

掌握 Symfony HTTP Cache = 熟悉 HTTP 快取標準。核心建議:

  1. 優先使用過期模型 (s-maxage),簡單高效
  2. 動態內容用驗證模型 (ETag/Last-Modified) + 效能優化模式
  3. 片段快取用 ESI,不同區塊不同 TTL
  4. 開發用內建 AppCache,生產上 Varnish,零代碼遷移
  5. 避免手動快取失效,善用 HTTP 內建機制

深入閱讀:Varnish 文檔、RFC 7234。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved