PHP項目中,尤其是在高並發大流量的場景中,如何提升PHP的響應時間,是一項十分重要的工作。
而Opcache又是優化PHP性能不可缺失的組件,尤其是應用了PHP框架的項目中,作用更是明顯。
1. 概述
在理解 OPCache 功能之前,我們有必要先理解PHP-FPM + Nginx 的工作機制,以及PHP腳本解釋執行的機制。
1.1 PHP-FPM + Nginx 的工作機制
請求從Web瀏覽器到Nginx,再到PHP處理完成,一共要經歷如下五個步驟:
第一步:啟動服務
-
啟動PHP-FPM。PHP-FPM 支持兩種通信模式:TCP socket和Unix socket;
-
PHP-FPM 會啟動兩種類型的進程:Master 進程 和 Worker 進程,前者負責監控端口、分配任務、管理Worker進程;后者就是PHP的cgi程序,負責解釋編譯執行PHP腳本。
-
啟動Nginx。首先會載入 ngx_http_fastcgi_module 模塊,初始化FastCGI執行環境,實現FastCGI協議請求代理
-
這里要注意:fastcgi的worker進程(cgi進程),是由PHP-FPM來管理,不是Nginx。Nginx只是代理
第二步:Request => Nginx
-
Nginx 接收請求,並基於location配置,選擇一個合適handler
-
這里就是代理PHP的 handler
第三步:Nginx => PHP-FPM
-
Nginx 把請求翻譯成fastcgi請求
-
通過TCP socket/Unix Socket 發送給PHP-FPM 的master進程
第四步:PHP-FPM Master => Worker
-
PHP-FPM master 進程接收到請求
-
分配Worker進程執行PHP腳本,如果沒有空閑的Worker,返回502錯誤
-
Worker(php-cgi)進程執行PHP腳本,如果超時,返回504錯誤
-
處理結束,返回結果
第五步:PHP-FPM Worker => Master => Nginx
-
PHP-FPM Worker 進程返回處理結果,並關閉連接,等待下一個請求
-
PHP-FPM Master 進程通過Socket 返回處理結果
-
Nginx Handler順序將每一個響應buffer發送給第一個filter → 第二個 → 以此類推 → 最終響應發送給客戶端
1.2 PHP腳本解釋執行的機制
了解了PHP + Nginx 整體的處理流程后,我們接下來看一下PHP腳本具體執行流程,
首先我們看一個實例:
<?php if (!empty($_POST)) { echo "Response Body POST: ", json_encode($_POST), "\n"; } if (!empty($_GET)) { echo "Response Body GET: ", json_encode($_GET), "\n"; }
我們分析一下執行過程:
-
php初始化執行環節,啟動Zend引擎,加載注冊的擴展模塊
-
初始化后讀取腳本文件,Zend引擎對腳本文件進行詞法分析(lex),語法分析(bison),生成語法樹
-
Zend 引擎編譯語法樹,生成opcode,
-
Zend 引擎執行opcode,返回執行結果
在PHP cli模式下,每次執行PHP腳本,四個步驟都會依次執行一遍;
在PHP-FPM模式下,步驟1)在PHP-FPM啟動時執行一次,后續的請求中不再執行;步驟2)~4)每個請求都要執行一遍;
其實步驟2)、3)生成的語法樹和opcode,同一個PHP腳本每次運行的結果都是一樣的,
在PHP-FPM模式下,每次請求都要處理一遍,是對系統資源極大的浪費,那么有沒有辦法優化呢?
當然有,如:
-
OPCache:前身是Zend Optimizer+ ,是 Zend Server 的一個開源組件;官方出品,強力推薦
-
APC:Alternative PHP Cache 是一個開放自由的 PHP opcode 緩存組件,用於緩存、優化 PHP 中間代碼;已經不更新了不推薦
-
APCu:是APC的一個分支,共享內存,緩存用戶數據,不能緩存opcode,可以配合Opcache 使用
-
eAccelerate:同樣是不更新了,不推薦
-
xCache:不再推薦使用了
2. OPCache 介紹
OPCache 是Zend官方出品的,開放自由的 opcode 緩存擴展,還具有代碼優化功能,省去了每次加載和解析 PHP 腳本的開銷。
PHP 5.5.0 及后續版本中已經綁定了 OPcache 擴展。
緩存兩類內容:
-
OPCode
-
Interned String,如注釋、變量名等
3. OPCache 原理
OPCache緩存的機制主要是:將編譯好的操作碼放入共享內存,提供給其他進程訪問。
這里就涉及到內存共享機制,另外所有內存資源操作都有鎖的問題,我們一一解讀。
3.1 共享內存
UNIX/Linux 系統提供很多種進程間內存共享的方式:
-
System-V shm API: System V共享內存,
-
sysv shm是持久化的,除非被一個進程明確的刪除,否則它始終存在於內存里,直到系統關機;
-
mmap API:
-
mmap映射的內存在不是持久化的,如果進程關閉,映射隨即失效,除非事先已經映射到了一個文件上
-
內存映射機制mmap是POSIX標准的系統調用,有匿名映射和文件映射兩種
-
mmap的一大優點是把文件映射到進程的地址空間
-
避免了數據從用戶緩沖區到內核page cache緩沖區的復制過程;
-
當然還有一個優點就是不需要頻繁的read/write系統調用
-
POSIX API:System V 的共享內存是過時的, POSIX共享內存提供了使用更簡單、設計更合理的API.
-
Unix socket API
OPCache 使用了前三個共享內存機制,根據配置或者默認mmap 內存共享模式。
依據PHP字節碼緩存的場景,OPCache的內存管理設計非常簡單,快速讀寫,不釋放內存,過期數據置為Wasted。
當Wasted內存大於設定值時,自動重啟OPCache機制,清空並重新生成緩存。
3.2 互斥鎖
任何內存資源的操作,都涉及到鎖的機制。
共享內存:一個單位時間內,只允許一個進程執行寫操作,允許多個進程執行讀操作;
寫操作同時,不阻止讀操作,以至於很少有鎖死的情況。
這就引發另外一個問題:新代碼、大流量場景,進程排隊執行緩存opcode操作;重復寫入,導致資源浪費。
4. OPCache 緩存解讀
OPCache 是官方的Opcode 緩存解決方案,在PHP5.5版本之后,已經打包到PHP源碼中一起發布。
它將PHP編譯產生的字節碼以及數據緩存到共享內存中, 在每次請求,從緩存中直接讀取編譯后的opcode,進行執行。
通過節省腳本的編譯過程,提高PHP的運行效率。
如果正在使用APC擴展,做同樣的工作,現在強烈推薦OPCache來代替,尤其是PHP7中。
4.1 OPCode 緩存
Opcache 會緩存OPCode以及如下內容:
-
PHP腳本涉及到的函數
-
PHP腳本中定義的Class
-
PHP腳本文件路徑
-
PHP腳本OPArray
-
PHP腳本自身結構/內容
4.2 Interned String 緩存
首先我們需要理解,什么是 Interned String?
在PHP5.4的時候, 引入了Interned String機制, 用於優化PHP對字符串的存儲和處理。
尤其是處理大塊的字符串,比如PHP doces時,Interned String 可以優化內存。
Interned String 緩存的內容包括:變量名稱、類名、方法名、字符串、注釋等。
在PHP-FPM模式中,Interned String 緩存字符,僅限於Worker 進程內部。
而緩存到OPCache中,那么Worker進程之間可以使用 Interned String 緩存的字符串,節省內存。
我們需要注意一個事情,在PHP開發中,一般會有大段的注釋,也會被緩存到OPCache中。
可以通過php.ini的配置,關閉注釋的緩存。
但是,像Zend Framework等框架中,會引用注釋,所以,是否關閉注釋的緩存,需要區別對待。
5. OPCache 更新策略
是緩存,都存在過期,以及更新策略等。
而OPCache的更新策略非常簡單,到期數據置為Wasted,達到設定值,清空緩存,重建緩存。
這里需要注意:在高流量的場景下,重建緩存是一件非常耗費資源的事兒。
OPCache 在創建緩存時並不會阻止其他進程讀取。
這會導致大量進程反復新建緩存。所以,不要設置OPCache過期時間
每次發布新代碼時,都會出現反復新建緩存的情況。如何避免呢?
-
不要在高峰期發布代碼,這是任何情況下都要遵守的規則
-
代碼預熱,比如使用腳本批量調PHP 訪問URL,或者使用OPCache 暴露的API 如
opcache_compile_file()
進行編譯緩存
6. OPCache 的配置
6.1 內存配置
-
opcache.preferred_memory_model="mmap"
OPcache 首選的內存模塊。如果留空,OPcache 會選擇適用的模塊, 通常情況下,自動選擇就可以滿足需求。可選值包括:mmap
,shm
,posix
以及win32
。 -
opcache.memory_consumption=64
OPcache 的共享內存大小,以兆字節為單位,默認64M
-
opcache.interned_strings_buffer=4
用來存儲臨時字符串的內存大小,以兆字節為單位,默認4M
-
opcache.max_wasted_percentage=5
浪費內存的上限,以百分比計。如果達到此上限,那么 OPcache 將產生重新啟動續發事件。默認5
6.2 允許緩存的文件數量以及大小
-
opcache.max_accelerated_files=2000
OPcache 哈希表中可存儲的腳本文件數量上限。真實的取值是在質數集合{ 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 }
中找到的第一個大於等於設置值的質數。設置值取值范圍最小值是200
,最大值在 PHP 5.5.6 之前是100000
,PHP 5.5.6 及之后是1000000
。默認值2000
-
opcache.max_file_size=0
以字節為單位的緩存的文件大小上限。設置為 0 表示緩存全部文件。默認值0
6.3 注釋相關的緩存
-
opcache.load_comments
boolean
如果禁用,則即使文件中包含注釋,也不會加載這些注釋內容。本選項可以和opcache.save_comments
一起使用,以實現按需加載注釋內容。 -
opcache.fast_shutdown
boolean 如果啟用,則會使用快速停止續發事件。所謂快速停止續發事件是指依賴 Zend 引擎的內存管理模塊 一次釋放全部請求變量的內存,而不是依次釋放每一個已分配的內存塊。
6.4 二級緩存的配置
-
opcache.file_cache
配置二級緩存目錄並啟用二級緩存。啟用二級緩存可以在 SHM 內存滿了、服務器重啟或者重置 SHM 的時候提高性能。默認值為空字符串""
,表示禁用基於文件的緩存。 -
opcache.file_cache_only
boolean
啟用或禁用在共享內存中的 opcode 緩存。 -
opcache.file_cache_consistency_checks
boolean
當從文件緩存中加載腳本的時候,是否對文件的校驗和進行驗證。 -
opcache.file_cache_fallback
boolean
在 Windows 平台上,當一個進程無法附加到共享內存的時候, 使用基於文件的緩存,也即:opcache.file_cache_only=1
。需要顯示的啟用文件緩存。
原文地址:http://kevhu.com/php/551#
掃二維碼,關注更多PHP資訊!