# PHP 運作 ###### tags: `PHP` ## Server 運作原理 - 當 webserver 收到請求,會先確認請求的檔案是什麼,如果是靜態檔案,將會從系統中找出文件位置,發送回呼叫端。 - 如果請求的檔案是 php,將會交給 PHP 解析器處理,CGI 協議規範 server 跟 PHP 解析器數據交換格式,PHP 解析處理完成後,再返回資料給 web server,web server 再發送回呼叫端,PHP 解析器有兩種,官方的 Zend Engine 跟 FB 開發的 HHVM。 ```graphviz digraph G { node[shape=box]; client [label="呼叫端"]; server [label="Web Server"] client->server static [label="靜態檔案(.html / .js)"] php [label="php 檔案"] server->static server->php parser [label="PHP 解析器(Zend Engine/HHVM)"] php->parser [label="CGI 協議"] } ``` ### Nginx nginx 常用來做負載均衡跟反向代理等工作,不依賴 thread 去處理 request,而是使用非同步的 event-driven 架構 nginx 有區分 master 跟 worker,master 管理著 worker,啟動後會 fork 出多個 worker process 待命,在 config 設定中的 `worker_processes` 可以設定 worker process 數目,如果設定成 `auto` 會自動設定成 CPU 核心數 圖中可看出 naginx 啟用 1 個 master process 跟 2 個 worker process ![](https://i.imgur.com/7iuSONp.png) worker process 會監聽事件,當有 client 端觸發請求 enent 時,worker process 會去處理分析請求並轉發至 I/O 操作或 PHP 解析器,針對 I/O 操作,nginx 使用的是 [epoll I/O 事件通知](https://zh.wikipedia.org/wiki/Epoll),PHP 解析器下面會做說明。worker process 會監聽相關處理完成的 evnet,再回覆給 client 端 ## PHP-FPM 早期 PHP 解析器運作會根據 php.ini 設定,初始化執行環境,然後處理 server 端送來的請求,處理完後關閉 Process。 但每次請求都要重新初始環境,速度太慢,因此有了 PHP-FPM 管理 PHP Process,PHP-FPM 會在服務啟動時建立 Master 跟數個 Workers,而 Web Server 會透過 TCP socket 或 Unix Socket 發送資料給 PHP-FPM Master,Master 再去分配給底下的 Workers 執行,執行完畢再透過 Master 回覆給 Web Server。 PHP-FPM 可控制記憶體使用量、workers 數目,還可平滑重載 php.ini 等配置,新的 worker 使用新的配置。 例如 nginx server 的 `fastcgi_pass` 配置 `unix:/run/php-fpm/php-fpm.sock`,就是透過 Unix Socket 發送給在本地的 php-fpm,如果改成 `{php-fpm-ip}:{php-fpm-port}`,就是透過 TCP socket 發送給其他 php-fpm server。 ```graphviz digraph G { node[shape=box]; server [label="Web Server"] master [label="PHP-FPM Master"] worker_1 [label="PHP-FPM Worker 1"] worker_2 [label="PHP-FPM Worker 2"] worker_3 [label="PHP-FPM Worker 3"] worker_4 [label="PHP-FPM Worker 4"] server->master [label="FastCGI 協議"] master->worker_1 master->worker_2 master->worker_3 master->worker_4 } ``` ## Zend Engine PHP 初始化會啟動 Zend 引擎,並載入擴展模塊,執行 php 文檔主要分成兩大步驟,預編譯(compile)以及執行(excuse),預編譯會進行詞法(lex/tokenize)跟語法(parse)分析,生成 opcode,之後再執行 opcode 並輸出結果。 ![](https://i.imgur.com/uJLvdAd.png) 每個 worker 在解析 php 文檔時都會執行預編譯,生成 opcode,假設文檔沒有更動,opcode 其實是一樣的,預編譯過程會浪費時間,因此出現了 opcache ,其實就是把該文檔的 opcode cache 起來,下一次執行相同文檔時,不必再編譯一次。當然,如果文檔有更新,必須清除 cache 可以透過 `opcache.validate_timestamps` & `opcache.revalidate_freq` 設定是否檢查文檔及檢查文檔的頻率。 使用 opcache 要注意,在高流量的情況下,重建 opcache 是非常消耗資源的一件事,因此正式環境必須考思考程式更新及 opcache 重置的問題。