<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>PHP 知識卡 on Tarragon</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/</link><description>Recent content in PHP 知識卡 on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Mon, 05 Oct 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/index.xml" rel="self" type="application/rss+xml"/><item><title>SAPI（Server API）</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/sapi/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/php/knowledge-cards/sapi/</guid><description>&lt;p>SAPI（Server API）是 PHP 核心與外部環境之間的介面層：它把外部環境帶來的請求翻譯成 PHP 認得的形式（填好 &lt;code>$_SERVER&lt;/code>、&lt;code>$_GET&lt;/code>，把請求內容交給 &lt;code>php://input&lt;/code>），再把 PHP 的輸出與回應標頭交回外部環境。同一份 PHP 核心配上不同的 SAPI，就是不同的執行方式。可先對照 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/shared-nothing-request/" data-link-title="Shared-nothing 請求模型" data-link-desc="要判斷 PHP 的全域變數、static 變數或類別靜態屬性會不會留到下一個請求，或評估換成常駐 worker（Octane、FrankenPHP worker）時哪些程式碼會出問題時">Shared-nothing 請求模型&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>SAPI 位在 PHP 核心（Zend 引擎與擴充）與 web 伺服器、命令列之間。它決定 PHP 直譯器住在哪個程序、那個程序活多久，這兩件事又決定 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/opcache/" data-link-title="OPcache" data-link-desc="部署新版程式碼之後頁面還在跑舊版、評估 OPcache 的大小設定，或比較不同執行模型下 PHP 的效能差距時">OPcache&lt;/a> 有沒有編譯結果可以重用。&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php&lt;/a> 是 SAPI 的一種（&lt;code>apache2handler&lt;/code>），PHP-FPM 是另一種（&lt;code>fpm-fcgi&lt;/code>）。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>讀目前的 SAPI&lt;/strong>：程式裡的 &lt;code>PHP_SAPI&lt;/code> 常數或 &lt;code>php_sapi_name()&lt;/code>。命令列執行是 &lt;code>cli&lt;/code>，PHP-FPM 是 &lt;code>fpm-fcgi&lt;/code>，mod_php 是 &lt;code>apache2handler&lt;/code>，&lt;code>php-cgi&lt;/code> 是 &lt;code>cgi-fcgi&lt;/code>，FrankenPHP 是 &lt;code>frankenphp&lt;/code>。&lt;/li>
&lt;li>&lt;strong>同一支程式、兩種行為&lt;/strong>：&lt;code>php-cgi&lt;/code> 被 web 伺服器以 CGI 啟動時處理一個請求就結束，以 FastCGI 模式啟動時常駐，SAPI 名稱都是 &lt;code>cgi-fcgi&lt;/code>。&lt;/li>
&lt;li>&lt;strong>設定檔依 SAPI 分開&lt;/strong>：很多發行版把 &lt;code>php.ini&lt;/code> 分成 &lt;code>cli/&lt;/code> 與 &lt;code>fpm/&lt;/code> 兩份，命令列改的設定不影響網頁請求。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>部署時要先知道服務跑的是哪個 SAPI，因為程序壽命、執行帳號、設定檔位置與 &lt;code>.htaccess&lt;/code> 能不能改 PHP 設定都跟著它變。完整的比較見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/request-lifecycle-and-sapi/" data-link-title="PHP 的請求生命週期與 SAPI：直譯器的位置、程序壽命與狀態壽命" data-link-desc="說明一個 HTTP 請求怎麼變成一次 PHP 執行：PHP 在程序啟動與每個請求開始、結束時各做什麼，SAPI 這一層介面怎麼讓同一個直譯器接上 CGI、Apache、FastCGI 與常駐伺服器，以及用直譯器住在哪裡、執行它的程序活多久、應用程式的狀態活多久這組問題比較各種執行模型">PHP 的請求生命週期與 SAPI&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>SAPI（Server API）是 PHP 核心與外部環境之間的介面層：它把外部環境帶來的請求翻譯成 PHP 認得的形式（填好 <code>$_SERVER</code>、<code>$_GET</code>，把請求內容交給 <code>php://input</code>），再把 PHP 的輸出與回應標頭交回外部環境。同一份 PHP 核心配上不同的 SAPI，就是不同的執行方式。可先對照 <a href="/blog/programming/php/knowledge-cards/shared-nothing-request/" data-link-title="Shared-nothing 請求模型" data-link-desc="要判斷 PHP 的全域變數、static 變數或類別靜態屬性會不會留到下一個請求，或評估換成常駐 worker（Octane、FrankenPHP worker）時哪些程式碼會出問題時">Shared-nothing 請求模型</a> 與 <a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>SAPI 位在 PHP 核心（Zend 引擎與擴充）與 web 伺服器、命令列之間。它決定 PHP 直譯器住在哪個程序、那個程序活多久，這兩件事又決定 <a href="/blog/programming/php/knowledge-cards/opcache/" data-link-title="OPcache" data-link-desc="部署新版程式碼之後頁面還在跑舊版、評估 OPcache 的大小設定，或比較不同執行模型下 PHP 的效能差距時">OPcache</a> 有沒有編譯結果可以重用。<a href="/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php</a> 是 SAPI 的一種（<code>apache2handler</code>），PHP-FPM 是另一種（<code>fpm-fcgi</code>）。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong>讀目前的 SAPI</strong>：程式裡的 <code>PHP_SAPI</code> 常數或 <code>php_sapi_name()</code>。命令列執行是 <code>cli</code>，PHP-FPM 是 <code>fpm-fcgi</code>，mod_php 是 <code>apache2handler</code>，<code>php-cgi</code> 是 <code>cgi-fcgi</code>，FrankenPHP 是 <code>frankenphp</code>。</li>
<li><strong>同一支程式、兩種行為</strong>：<code>php-cgi</code> 被 web 伺服器以 CGI 啟動時處理一個請求就結束，以 FastCGI 模式啟動時常駐，SAPI 名稱都是 <code>cgi-fcgi</code>。</li>
<li><strong>設定檔依 SAPI 分開</strong>：很多發行版把 <code>php.ini</code> 分成 <code>cli/</code> 與 <code>fpm/</code> 兩份，命令列改的設定不影響網頁請求。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>部署時要先知道服務跑的是哪個 SAPI，因為程序壽命、執行帳號、設定檔位置與 <code>.htaccess</code> 能不能改 PHP 設定都跟著它變。完整的比較見 <a href="/blog/programming/php/01-server-runtime/request-lifecycle-and-sapi/" data-link-title="PHP 的請求生命週期與 SAPI：直譯器的位置、程序壽命與狀態壽命" data-link-desc="說明一個 HTTP 請求怎麼變成一次 PHP 執行：PHP 在程序啟動與每個請求開始、結束時各做什麼，SAPI 這一層介面怎麼讓同一個直譯器接上 CGI、Apache、FastCGI 與常駐伺服器，以及用直譯器住在哪裡、執行它的程序活多久、應用程式的狀態活多久這組問題比較各種執行模型">PHP 的請求生命週期與 SAPI</a>。</p>
]]></content:encoded></item><item><title>Shared-nothing 請求模型</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/shared-nothing-request/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/php/knowledge-cards/shared-nothing-request/</guid><description>&lt;p>Shared-nothing 請求模型的核心概念是「每個請求結束時，PHP 清空這個請求裡由 PHP 程式碼建立的全部狀態」：全域變數、函式內的 &lt;code>static&lt;/code> 變數、類別的靜態屬性、物件與一般的資源都在請求結束時釋放，下一個請求從空白開始，即使由同一個程序處理。可先對照 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/opcache/" data-link-title="OPcache" data-link-desc="部署新版程式碼之後頁面還在跑舊版、評估 OPcache 的大小設定，或比較不同執行模型下 PHP 的效能差距時">OPcache&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>Shared-nothing 是 PHP 傳統執行方式（CGI、&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php&lt;/a>、&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a>）共有的狀態壽命：程序可能活過很多請求，狀態只活一個請求。它清的是 PHP 程式碼建立的狀態，程序層的資源（OPcache、持久連線、APCu）不在範圍內。常駐 worker 模型（FrankenPHP 的 worker 模式、Laravel Octane）放棄了它。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>同一個 PID、計數器仍是 1&lt;/strong>：在 PHP-FPM 下對一支每次把 &lt;code>static&lt;/code> 變數加一的腳本連續請求，同一個 worker 處理第二次時計數器仍然是 1。&lt;/li>
&lt;li>&lt;strong>常駐 worker 下計數器累加&lt;/strong>：同一段程式碼放進 Octane 或 FrankenPHP worker，計數器變成 1、2、3……，往靜態陣列塞資料的程式會讓記憶體隨請求數線性增長。&lt;/li>
&lt;li>&lt;strong>框架每個請求重新啟動&lt;/strong>：Laravel 這類框架每個請求都要重建服務容器、讀設定、建路由表，這段成本是 shared-nothing 的代價。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>在 shared-nothing 下，程式可以放心把請求資料放在靜態屬性裡；要換成常駐 worker 之前，這類寫法都要改掉。實測與要避開的寫法見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/long-running-workers/" data-link-title="常駐 worker 模型：FrankenPHP worker 模式、Laravel Octane 與跨請求殘留的狀態" data-link-desc="說明讓 PHP 應用程式只啟動一次、在記憶體裡連續處理請求的常駐 worker 模型：它省下的是每個請求重新啟動框架的成本，失去的是 shared-nothing 的隔離；以 FrankenPHP 的 worker 模式與 Laravel Octane 實測靜態屬性跨請求累積、記憶體隨請求數線性增長與 max-requests 重啟，以及寫給這個模型的程式要避開的寫法">常駐 worker 模型&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>Shared-nothing 請求模型的核心概念是「每個請求結束時，PHP 清空這個請求裡由 PHP 程式碼建立的全部狀態」：全域變數、函式內的 <code>static</code> 變數、類別的靜態屬性、物件與一般的資源都在請求結束時釋放，下一個請求從空白開始，即使由同一個程序處理。可先對照 <a href="/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI</a> 與 <a href="/blog/programming/php/knowledge-cards/opcache/" data-link-title="OPcache" data-link-desc="部署新版程式碼之後頁面還在跑舊版、評估 OPcache 的大小設定，或比較不同執行模型下 PHP 的效能差距時">OPcache</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>Shared-nothing 是 PHP 傳統執行方式（CGI、<a href="/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php</a>、<a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a>）共有的狀態壽命：程序可能活過很多請求，狀態只活一個請求。它清的是 PHP 程式碼建立的狀態，程序層的資源（OPcache、持久連線、APCu）不在範圍內。常駐 worker 模型（FrankenPHP 的 worker 模式、Laravel Octane）放棄了它。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong>同一個 PID、計數器仍是 1</strong>：在 PHP-FPM 下對一支每次把 <code>static</code> 變數加一的腳本連續請求，同一個 worker 處理第二次時計數器仍然是 1。</li>
<li><strong>常駐 worker 下計數器累加</strong>：同一段程式碼放進 Octane 或 FrankenPHP worker，計數器變成 1、2、3……，往靜態陣列塞資料的程式會讓記憶體隨請求數線性增長。</li>
<li><strong>框架每個請求重新啟動</strong>：Laravel 這類框架每個請求都要重建服務容器、讀設定、建路由表，這段成本是 shared-nothing 的代價。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>在 shared-nothing 下，程式可以放心把請求資料放在靜態屬性裡；要換成常駐 worker 之前，這類寫法都要改掉。實測與要避開的寫法見 <a href="/blog/programming/php/01-server-runtime/long-running-workers/" data-link-title="常駐 worker 模型：FrankenPHP worker 模式、Laravel Octane 與跨請求殘留的狀態" data-link-desc="說明讓 PHP 應用程式只啟動一次、在記憶體裡連續處理請求的常駐 worker 模型：它省下的是每個請求重新啟動框架的成本，失去的是 shared-nothing 的隔離；以 FrankenPHP 的 worker 模式與 Laravel Octane 實測靜態屬性跨請求累積、記憶體隨請求數線性增長與 max-requests 重啟，以及寫給這個模型的程式要避開的寫法">常駐 worker 模型</a>。</p>
]]></content:encoded></item><item><title>mod_php</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/</guid><description>&lt;p>mod_php 是編譯成 Apache 模組的 PHP，Apache 啟動時用 &lt;code>LoadModule&lt;/code> 載入，之後 Apache 的每個子程序裡都有一份 PHP 直譯器，收到 &lt;code>.php&lt;/code> 請求就在自己的程序裡執行。它的 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI&lt;/a> 名稱是 &lt;code>apache2handler&lt;/code>。可先對照 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/apache-mpm/" data-link-title="Apache MPM（Multi-Processing Module）" data-link-desc="在 Apache 設定或 httpd -V 看到 prefork、worker、event，或要判斷 mod_php 為什麼讓 Apache 只能用 prefork、換成 PHP-FPM 之後能改用哪一種時">Apache MPM&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>mod_php 把直譯器放進 web 伺服器的程序裡：省掉 CGI 每請求啟動程序的成本，代價是 PHP 的壽命、執行帳號與設定都綁在 Apache 上。沒有以 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/zts/" data-link-title="ZTS（Zend Thread Safety）" data-link-desc="在 php -i 看到 Thread Safety 是 enabled 或 disabled，或要判斷某個 PHP 能不能跑在多執行緒的伺服器（Apache worker/event MPM、FrankenPHP）裡時">ZTS&lt;/a> 編譯的 mod_php 只能搭配 prefork MPM。PHP 官方文件對新部署的建議是改用 PHP-FPM 搭配 Apache 的 &lt;code>mod_proxy_fcgi&lt;/code>。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>apache2ctl -M&lt;/code> 列出 &lt;code>php_module&lt;/code> 與 &lt;code>mpm_prefork_module&lt;/code>&lt;/strong>：多數發行版的 mod_php 沒有開 ZTS，&lt;code>php -i&lt;/code> 顯示 &lt;code>Thread Safety =&amp;gt; disabled&lt;/code>。&lt;/li>
&lt;li>&lt;strong>送靜態檔的子程序也映射著 &lt;code>libphp&lt;/code>&lt;/strong>：每個子程序都帶著 PHP，不論它在處理什麼請求。&lt;/li>
&lt;li>&lt;strong>所有站台以 Apache 的帳號（如 &lt;code>www-data&lt;/code>）執行&lt;/strong>：PHP 產生的檔案屬於這個帳號。&lt;/li>
&lt;li>&lt;strong>&lt;code>.htaccess&lt;/code> 的 &lt;code>php_value&lt;/code>&lt;/strong>：mod_php 註冊了這個指令；沒載入 mod_php 的 Apache 讀到它會回 500（&lt;code>Invalid command 'php_value'&lt;/code>），而且整個目錄都壞掉。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>從 mod_php 搬到 PHP-FPM 時，&lt;code>.htaccess&lt;/code> 裡的 &lt;code>php_value&lt;/code> / &lt;code>php_flag&lt;/code> 要移到 &lt;code>.user.ini&lt;/code>（見 &lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini&lt;/a>）。完整的行為與實測見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/mod-php/" data-link-title="mod_php：PHP 直譯器嵌進 Apache 程序的執行模型" data-link-desc="說明 mod_php 怎麼把 PHP 直譯器載入 Apache 的每個子程序：為什麼它要搭配 prefork MPM、每個子程序都帶著 PHP 對記憶體與並發的影響、所有站台共用 Apache 的執行帳號、php.ini 與 .htaccess 的設定方式，以及從 mod_php 搬到 PHP-FPM 時 .htaccess 的 php_value 與 .user.ini 的差異">mod_php&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>mod_php 是編譯成 Apache 模組的 PHP，Apache 啟動時用 <code>LoadModule</code> 載入，之後 Apache 的每個子程序裡都有一份 PHP 直譯器，收到 <code>.php</code> 請求就在自己的程序裡執行。它的 <a href="/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI</a> 名稱是 <code>apache2handler</code>。可先對照 <a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a> 與 <a href="/blog/devops/infra/knowledge-cards/apache-mpm/" data-link-title="Apache MPM（Multi-Processing Module）" data-link-desc="在 Apache 設定或 httpd -V 看到 prefork、worker、event，或要判斷 mod_php 為什麼讓 Apache 只能用 prefork、換成 PHP-FPM 之後能改用哪一種時">Apache MPM</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>mod_php 把直譯器放進 web 伺服器的程序裡：省掉 CGI 每請求啟動程序的成本，代價是 PHP 的壽命、執行帳號與設定都綁在 Apache 上。沒有以 <a href="/blog/programming/php/knowledge-cards/zts/" data-link-title="ZTS（Zend Thread Safety）" data-link-desc="在 php -i 看到 Thread Safety 是 enabled 或 disabled，或要判斷某個 PHP 能不能跑在多執行緒的伺服器（Apache worker/event MPM、FrankenPHP）裡時">ZTS</a> 編譯的 mod_php 只能搭配 prefork MPM。PHP 官方文件對新部署的建議是改用 PHP-FPM 搭配 Apache 的 <code>mod_proxy_fcgi</code>。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>apache2ctl -M</code> 列出 <code>php_module</code> 與 <code>mpm_prefork_module</code></strong>：多數發行版的 mod_php 沒有開 ZTS，<code>php -i</code> 顯示 <code>Thread Safety =&gt; disabled</code>。</li>
<li><strong>送靜態檔的子程序也映射著 <code>libphp</code></strong>：每個子程序都帶著 PHP，不論它在處理什麼請求。</li>
<li><strong>所有站台以 Apache 的帳號（如 <code>www-data</code>）執行</strong>：PHP 產生的檔案屬於這個帳號。</li>
<li><strong><code>.htaccess</code> 的 <code>php_value</code></strong>：mod_php 註冊了這個指令；沒載入 mod_php 的 Apache 讀到它會回 500（<code>Invalid command 'php_value'</code>），而且整個目錄都壞掉。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>從 mod_php 搬到 PHP-FPM 時，<code>.htaccess</code> 裡的 <code>php_value</code> / <code>php_flag</code> 要移到 <code>.user.ini</code>（見 <a href="/blog/devops/infra/knowledge-cards/php-ini/" data-link-title="php.ini / .user.ini" data-link-desc="PHP 的執行期設定檔，控制記憶體上限、上傳大小、錯誤報告等 runtime 行為">php.ini / .user.ini</a>）。完整的行為與實測見 <a href="/blog/programming/php/01-server-runtime/mod-php/" data-link-title="mod_php：PHP 直譯器嵌進 Apache 程序的執行模型" data-link-desc="說明 mod_php 怎麼把 PHP 直譯器載入 Apache 的每個子程序：為什麼它要搭配 prefork MPM、每個子程序都帶著 PHP 對記憶體與並發的影響、所有站台共用 Apache 的執行帳號、php.ini 與 .htaccess 的設定方式，以及從 mod_php 搬到 PHP-FPM 時 .htaccess 的 php_value 與 .user.ini 的差異">mod_php</a>。</p>
]]></content:encoded></item><item><title>ZTS（Zend Thread Safety）</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/zts/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/php/knowledge-cards/zts/</guid><description>&lt;p>ZTS（Zend Thread Safety）是 PHP 的編譯選項（&lt;code>--enable-zts&lt;/code>）：開啟之後，PHP 把每個請求的狀態放在各執行緒自己的儲存區，而不是全域變數裡，多個執行緒才能同時執行 PHP 而不互相覆寫。沒有開 ZTS 的版本叫 NTS（Non Thread Safe）。可先對照 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>ZTS 決定 PHP 能不能住在一個多執行緒的程序裡。PHP 跑在獨立程序裡的執行方式（CGI、&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a>）不需要它，每個 worker 一次只處理一個請求；PHP 嵌在多執行緒伺服器裡時需要它：mod_php 要搭配 Apache 的 worker 或 event MPM、FrankenPHP 用執行緒當 worker。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>php -i | grep &amp;quot;Thread Safety&amp;quot;&lt;/code>&lt;/strong>：&lt;code>php:8.5-apache&lt;/code> 與 &lt;code>php:8.5-fpm-alpine&lt;/code> 是 &lt;code>disabled&lt;/code>，FrankenPHP 映像檔是 &lt;code>enabled&lt;/code>。&lt;/li>
&lt;li>&lt;strong>mod_php 只能用 prefork&lt;/strong>：PHP 官方安裝文件寫明，除非以 &lt;code>--enable-zts&lt;/code> 編譯，mod_php 需要 prefork MPM。&lt;/li>
&lt;li>&lt;strong>擴充也要支援&lt;/strong>：PHP 能以執行緒安全的方式執行，不代表每個擴充與它依賴的 C 函式庫都能。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>選執行模型時，ZTS 是前提條件之一：要讓 Apache 用執行緒、或採用 FrankenPHP，就要拿到 ZTS 版的 PHP 與相容的擴充。不需要執行緒時，NTS 版本較常見、擴充相容性也較好。&lt;/p></description><content:encoded><![CDATA[<p>ZTS（Zend Thread Safety）是 PHP 的編譯選項（<code>--enable-zts</code>）：開啟之後，PHP 把每個請求的狀態放在各執行緒自己的儲存區，而不是全域變數裡，多個執行緒才能同時執行 PHP 而不互相覆寫。沒有開 ZTS 的版本叫 NTS（Non Thread Safe）。可先對照 <a href="/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php</a> 與 <a href="/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>ZTS 決定 PHP 能不能住在一個多執行緒的程序裡。PHP 跑在獨立程序裡的執行方式（CGI、<a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a>）不需要它，每個 worker 一次只處理一個請求；PHP 嵌在多執行緒伺服器裡時需要它：mod_php 要搭配 Apache 的 worker 或 event MPM、FrankenPHP 用執行緒當 worker。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>php -i | grep &quot;Thread Safety&quot;</code></strong>：<code>php:8.5-apache</code> 與 <code>php:8.5-fpm-alpine</code> 是 <code>disabled</code>，FrankenPHP 映像檔是 <code>enabled</code>。</li>
<li><strong>mod_php 只能用 prefork</strong>：PHP 官方安裝文件寫明，除非以 <code>--enable-zts</code> 編譯，mod_php 需要 prefork MPM。</li>
<li><strong>擴充也要支援</strong>：PHP 能以執行緒安全的方式執行，不代表每個擴充與它依賴的 C 函式庫都能。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>選執行模型時，ZTS 是前提條件之一：要讓 Apache 用執行緒、或採用 FrankenPHP，就要拿到 ZTS 版的 PHP 與相容的擴充。不需要執行緒時，NTS 版本較常見、擴充相容性也較好。</p>
]]></content:encoded></item><item><title>PHP-FPM（FastCGI Process Manager）</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/</guid><description>&lt;p>PHP-FPM 是 PHP 內建的 FastCGI 程序管理器：一個 master 程序加一群常駐的 worker，master 負責讀設定、開 worker、補死掉的 worker 與處理訊號，worker 實際執行 PHP，透過 FastCGI 協定接收 web 伺服器轉來的請求。它的 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI&lt;/a> 名稱是 &lt;code>fpm-fcgi&lt;/code>。可先對照 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/opcache/" data-link-title="OPcache" data-link-desc="部署新版程式碼之後頁面還在跑舊版、評估 OPcache 的大小設定，或比較不同執行模型下 PHP 的效能差距時">OPcache&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>PHP-FPM 把直譯器放在 web 伺服器外面的常駐程序裡：程序活過很多請求，應用程式狀態仍然只活一個請求（&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/shared-nothing-request/" data-link-title="Shared-nothing 請求模型" data-link-desc="要判斷 PHP 的全域變數、static 變數或類別靜態屬性會不會留到下一個請求，或評估換成常駐 worker（Octane、FrankenPHP worker）時哪些程式碼會出問題時">shared-nothing&lt;/a>）。它只講 FastCGI，所以前面一定要有一個講 HTTP 的 web 伺服器（&lt;a href="https://tarrragon.github.io/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx&lt;/a> 或 Apache 的 &lt;code>mod_proxy_fcgi&lt;/code>）。PHP 5.3.3 起隨 PHP 本體發行。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>pm.max_children&lt;/code> 決定並發上限&lt;/strong>：一個 worker 一次處理一個請求，超過上限的請求排隊，status 頁的 &lt;code>max listen queue&lt;/code> 會大於 0。&lt;/li>
&lt;li>&lt;strong>502 加上「Connection reset by peer」&lt;/strong>：worker 被 &lt;code>request_terminate_timeout&lt;/code> 終止、或 reload／停機時沒有等進行中的請求（&lt;code>process_control_timeout&lt;/code> 預設 0）。&lt;/li>
&lt;li>&lt;strong>預設監聽 &lt;code>9000&lt;/code>&lt;/strong>：FastCGI 沒有驗證，PHP 官方文件要求 php-fpm 不能讓不受信任的網路連到。&lt;/li>
&lt;li>&lt;strong>pool&lt;/strong>：每個 pool 可以指定執行帳號，讓不同站台的 PHP 互相讀不到檔案。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>部署 PHP-FPM 要決定 &lt;code>max_children&lt;/code>（以最重請求的記憶體峰值估）、&lt;code>max_requests&lt;/code>、&lt;code>request_terminate_timeout&lt;/code> 與 &lt;code>process_control_timeout&lt;/code>，並把 FastCGI 埠限制在信任的網路內。各設定的實測見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/php-fpm/" data-link-title="PHP-FPM：常駐 PHP worker 的程序管理與主要的調校旋鈕" data-link-desc="說明 PHP-FPM 怎麼管理一群常駐的 PHP worker：pool 怎麼做到每個站台不同執行帳號、pm 的三種開程序策略、max_children 如何決定並發上限與請求排隊、max_requests 定期換掉 worker 控制記憶體、request_terminate_timeout 與 slowlog 怎麼處理跑太久的請求、status 頁怎麼讀，以及每一項對壓測數字的影響">PHP-FPM&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>PHP-FPM 是 PHP 內建的 FastCGI 程序管理器：一個 master 程序加一群常駐的 worker，master 負責讀設定、開 worker、補死掉的 worker 與處理訊號，worker 實際執行 PHP，透過 FastCGI 協定接收 web 伺服器轉來的請求。它的 <a href="/blog/programming/php/knowledge-cards/sapi/" data-link-title="SAPI（Server API）" data-link-desc="在 PHP_SAPI 或 phpinfo 看到 cli、fpm-fcgi、apache2handler、frankenphp 這類值，要知道它代表 PHP 以哪種方式被啟動、請求怎麼交進來時">SAPI</a> 名稱是 <code>fpm-fcgi</code>。可先對照 <a href="/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php</a> 與 <a href="/blog/programming/php/knowledge-cards/opcache/" data-link-title="OPcache" data-link-desc="部署新版程式碼之後頁面還在跑舊版、評估 OPcache 的大小設定，或比較不同執行模型下 PHP 的效能差距時">OPcache</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>PHP-FPM 把直譯器放在 web 伺服器外面的常駐程序裡：程序活過很多請求，應用程式狀態仍然只活一個請求（<a href="/blog/programming/php/knowledge-cards/shared-nothing-request/" data-link-title="Shared-nothing 請求模型" data-link-desc="要判斷 PHP 的全域變數、static 變數或類別靜態屬性會不會留到下一個請求，或評估換成常駐 worker（Octane、FrankenPHP worker）時哪些程式碼會出問題時">shared-nothing</a>）。它只講 FastCGI，所以前面一定要有一個講 HTTP 的 web 伺服器（<a href="/blog/devops/infra/knowledge-cards/nginx/" data-link-title="nginx" data-link-desc="高效能 Web Server 與 Reverse Proxy，以集中設定檔取代 Apache 的 .htaccess 分散設定">nginx</a> 或 Apache 的 <code>mod_proxy_fcgi</code>）。PHP 5.3.3 起隨 PHP 本體發行。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>pm.max_children</code> 決定並發上限</strong>：一個 worker 一次處理一個請求，超過上限的請求排隊，status 頁的 <code>max listen queue</code> 會大於 0。</li>
<li><strong>502 加上「Connection reset by peer」</strong>：worker 被 <code>request_terminate_timeout</code> 終止、或 reload／停機時沒有等進行中的請求（<code>process_control_timeout</code> 預設 0）。</li>
<li><strong>預設監聽 <code>9000</code></strong>：FastCGI 沒有驗證，PHP 官方文件要求 php-fpm 不能讓不受信任的網路連到。</li>
<li><strong>pool</strong>：每個 pool 可以指定執行帳號，讓不同站台的 PHP 互相讀不到檔案。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>部署 PHP-FPM 要決定 <code>max_children</code>（以最重請求的記憶體峰值估）、<code>max_requests</code>、<code>request_terminate_timeout</code> 與 <code>process_control_timeout</code>，並把 FastCGI 埠限制在信任的網路內。各設定的實測見 <a href="/blog/programming/php/01-server-runtime/php-fpm/" data-link-title="PHP-FPM：常駐 PHP worker 的程序管理與主要的調校旋鈕" data-link-desc="說明 PHP-FPM 怎麼管理一群常駐的 PHP worker：pool 怎麼做到每個站台不同執行帳號、pm 的三種開程序策略、max_children 如何決定並發上限與請求排隊、max_requests 定期換掉 worker 控制記憶體、request_terminate_timeout 與 slowlog 怎麼處理跑太久的請求、status 頁怎麼讀，以及每一項對壓測數字的影響">PHP-FPM</a>。</p>
]]></content:encoded></item><item><title>OPcache</title><link>https://tarrragon.github.io/blog/programming/php/knowledge-cards/opcache/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0800</pubDate><guid>https://tarrragon.github.io/blog/programming/php/knowledge-cards/opcache/</guid><description>&lt;p>OPcache 是 PHP 內建的擴充，把 PHP 檔案編譯好的 opcode（Zend 引擎執行的指令）存進共享記憶體，之後執行同一個檔案時跳過讀檔與編譯。它快取的是程式碼的編譯結果，不是資料。可先對照 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/shared-nothing-request/" data-link-title="Shared-nothing 請求模型" data-link-desc="要判斷 PHP 的全域變數、static 變數或類別靜態屬性會不會留到下一個請求，或評估換成常駐 worker（Octane、FrankenPHP worker）時哪些程式碼會出問題時">Shared-nothing 請求模型&lt;/a>。&lt;/p>
&lt;h2 id="概念位置">概念位置&lt;/h2>
&lt;p>OPcache 是程序層的資源，效果取決於執行 PHP 的程序活多久：CGI 每個請求一個新程序，快取永遠命中不了；&lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM&lt;/a> 與 &lt;a href="https://tarrragon.github.io/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php&lt;/a> 的父程序建立共享記憶體，所有 worker 共用同一份快取。PHP 8.5 起 OPcache 靜態編進 PHP 本體。它省掉的是編譯，框架每個請求的啟動流程它省不掉，那一段要靠常駐 worker 模型。&lt;/p>
&lt;h2 id="可觀察訊號與例子">可觀察訊號與例子&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>&lt;code>opcache_get_status()&lt;/code> 的 &lt;code>hits&lt;/code>&lt;/strong>：CGI 下永遠是 0；PHP-FPM 下跨 worker 累加。&lt;/li>
&lt;li>&lt;strong>改了檔案頁面不變&lt;/strong>：&lt;code>opcache.validate_timestamps=0&lt;/code> 時 OPcache 不檢查檔案，要 reload PHP-FPM 才生效；預設的 &lt;code>validate_timestamps=1&lt;/code>、&lt;code>revalidate_freq=2&lt;/code> 下，改檔後最多約 2 秒生效。&lt;/li>
&lt;li>&lt;strong>開與關的差距&lt;/strong>：Laravel 13 骨架的健康檢查請求，開 OPcache 約 3.8 ms、關掉約 49.3 ms。&lt;/li>
&lt;li>&lt;strong>快取滿了不報錯&lt;/strong>：放不進去的檔案每次重新編譯，從外面看只是變慢；要讀 &lt;code>free_memory&lt;/code> 與 &lt;code>num_cached_scripts&lt;/code>。&lt;/li>
&lt;/ul>
&lt;h2 id="設計責任">設計責任&lt;/h2>
&lt;p>部署要配合 &lt;code>validate_timestamps&lt;/code> 的設定：程式碼在映像檔裡的容器部署可以關掉檢查，覆寫檔案的部署要留著或在換完程式碼後 reload。&lt;code>memory_consumption&lt;/code> 與 &lt;code>max_accelerated_files&lt;/code> 依實際專案跑過主要請求後的統計調整。詳見 &lt;a href="https://tarrragon.github.io/blog/programming/php/01-server-runtime/opcache/" data-link-title="OPcache 與程序壽命：編譯結果的快取在各種執行模型下的效果" data-link-desc="說明 OPcache 快取的是什麼、它存在哪裡，以及為什麼它的效果取決於執行 PHP 的程序活多久：CGI 下命中次數永遠為零、PHP-FPM 的 worker 共用同一份快取；框架請求開與關 OPcache 的延遲差、memory_consumption 與 max_accelerated_files 的估算，以及部署新程式碼時 validate_timestamps 與 revalidate_freq 決定舊版還會執行多久">OPcache 與程序壽命&lt;/a>。&lt;/p></description><content:encoded><![CDATA[<p>OPcache 是 PHP 內建的擴充，把 PHP 檔案編譯好的 opcode（Zend 引擎執行的指令）存進共享記憶體，之後執行同一個檔案時跳過讀檔與編譯。它快取的是程式碼的編譯結果，不是資料。可先對照 <a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a> 與 <a href="/blog/programming/php/knowledge-cards/shared-nothing-request/" data-link-title="Shared-nothing 請求模型" data-link-desc="要判斷 PHP 的全域變數、static 變數或類別靜態屬性會不會留到下一個請求，或評估換成常駐 worker（Octane、FrankenPHP worker）時哪些程式碼會出問題時">Shared-nothing 請求模型</a>。</p>
<h2 id="概念位置">概念位置</h2>
<p>OPcache 是程序層的資源，效果取決於執行 PHP 的程序活多久：CGI 每個請求一個新程序，快取永遠命中不了；<a href="/blog/programming/php/knowledge-cards/php-fpm/" data-link-title="PHP-FPM（FastCGI Process Manager）" data-link-desc="在 Nginx 或容器環境看到 php-fpm、9000 埠、pool 設定或 502 Bad Gateway，要知道 PHP-FPM 負責什麼、哪些設定決定並發與記憶體時">PHP-FPM</a> 與 <a href="/blog/programming/php/knowledge-cards/mod-php/" data-link-title="mod_php" data-link-desc="接手 Apache 加 mod_php 的舊站、評估要不要換成 PHP-FPM，或遇到 .htaccess 的 php_value 在新環境回 500 時">mod_php</a> 的父程序建立共享記憶體，所有 worker 共用同一份快取。PHP 8.5 起 OPcache 靜態編進 PHP 本體。它省掉的是編譯，框架每個請求的啟動流程它省不掉，那一段要靠常駐 worker 模型。</p>
<h2 id="可觀察訊號與例子">可觀察訊號與例子</h2>
<ul>
<li><strong><code>opcache_get_status()</code> 的 <code>hits</code></strong>：CGI 下永遠是 0；PHP-FPM 下跨 worker 累加。</li>
<li><strong>改了檔案頁面不變</strong>：<code>opcache.validate_timestamps=0</code> 時 OPcache 不檢查檔案，要 reload PHP-FPM 才生效；預設的 <code>validate_timestamps=1</code>、<code>revalidate_freq=2</code> 下，改檔後最多約 2 秒生效。</li>
<li><strong>開與關的差距</strong>：Laravel 13 骨架的健康檢查請求，開 OPcache 約 3.8 ms、關掉約 49.3 ms。</li>
<li><strong>快取滿了不報錯</strong>：放不進去的檔案每次重新編譯，從外面看只是變慢；要讀 <code>free_memory</code> 與 <code>num_cached_scripts</code>。</li>
</ul>
<h2 id="設計責任">設計責任</h2>
<p>部署要配合 <code>validate_timestamps</code> 的設定：程式碼在映像檔裡的容器部署可以關掉檢查，覆寫檔案的部署要留著或在換完程式碼後 reload。<code>memory_consumption</code> 與 <code>max_accelerated_files</code> 依實際專案跑過主要請求後的統計調整。詳見 <a href="/blog/programming/php/01-server-runtime/opcache/" data-link-title="OPcache 與程序壽命：編譯結果的快取在各種執行模型下的效果" data-link-desc="說明 OPcache 快取的是什麼、它存在哪裡，以及為什麼它的效果取決於執行 PHP 的程序活多久：CGI 下命中次數永遠為零、PHP-FPM 的 worker 共用同一份快取；框架請求開與關 OPcache 的延遲差、memory_consumption 與 max_accelerated_files 的估算，以及部署新程式碼時 validate_timestamps 與 revalidate_freq 決定舊版還會執行多久">OPcache 與程序壽命</a>。</p>
]]></content:encoded></item></channel></rss>