120
Php編程
php安裝加速擴展 APC 詳解
一、 APC是什麼
Alternative PHP Cache (APC)是一種對PHP有效的開放源高速緩衝儲存器工具,他能夠緩存opcode的php中間碼。
PHP APC提供兩種緩存功能,即緩存Opcode(目標文件),我們稱之為apc_compiler_cache。同時它還提供一些接口用於PHP開發人員將用戶數據駐留在內存中,我們稱之為apc_user_cache。我們這裏主要控討php-apc的配置。
二、 安裝PHP APC
作為測試環境,我們這裏使用的是CentOS5.3(2.6.18-128.el5PAE) + Apache2.0(prefork) + php5.2。我們可以去pecl apc下載APC-3.0.19.tgz
# tar -xzvf APC-3.0.19.tgz
#cd APC-3.0.19
# /usr/bin/phpize
# ./configure --enable-apc --enable-mmap --enable-apc-spinlocks --disable-apc-pthreadmutex
#make
#make install
注意:我們這裏支持mmap,同時采用spinlocks自旋鎖。Spinlocks是Facebook推薦使用,同時也是APC開發者推薦使用的鎖機製。
三、 PHP APC 配置參數
如果你使用的係統環境跟我的測試環境是一樣的話,可以在/etc/php.d目錄下創建文件apc.ini,並且相關配置寫入/etc/php.d/apc.ini文件。這裏,我們挑了一些常用到的配置,並進行探討。把相關的配置放在一起解釋。
apc.enabled=1apc.enabled默認值是1,你可設成0禁用APC。如果你設置為0的時候,同樣把extension=apc.so也注釋掉(這樣可以節約內存資源)。一旦啟用了APC功能,則會緩存Opcodes到共享內存。
apc.shm_segments = 1
apc.shm_size = 30
APC既然把數據緩存在內存裏麵,我們就有必要對它進行內存資源限定。通過這二個配置可以限定APC可以使用的內存空間大小。apc.shm_segments指定了使用共享內存塊數,而apc.shm_size則指定了一塊共享內存空間大小,單位是M。所以,允許APC使用的內存大小應該是 apc.shm_segments * apc.shm_size = 30M。你可以調整一塊共享內存的大小空間。當然,一塊共享內存最大值是受操作係統限製的,即不能超過/proc/sys/kernel/shmmax大小。否則APC創建共享內存的時候,會失敗。在apc.shm_size達到了上限的時候,你可以通過設置apc.shm_segments來允許APC使用更多的內存空間。我們推薦,如果調用APC使用內存空間的話,先考濾apc.shm_size,後考濾apc.shm_segments。具體數值,可以根據apc.php監控情況進行規劃與調整。值得注意的是,每一次調整需要重啟httpd守護進程,這樣可以重新加載apc.so模塊。跟隨著httpd守護進程啟動,apc.so模塊就會加載。apc.so加載初始化的時候,通過mmap請求分配內存指定大小的內存,即apc.shm_size * apc.shm_segments。而且,這裏使用的是匿名內存映射方式,通過映射一個特殊設備/dev/zero,提供一個“大型”的,填滿了零的內存供APC管理。為了驗證以上陳述,我們注釋掉apc.ini配置,並且寫了以下php腳本觀察apc.so模塊初始化的分配的內存空間。
//@file: apc_load.php
if (!extension_loaded('apc'))
?>
#strace -p `cat /var/run/httpd.pid`
open("/var/www/html/apc_load.php", O_RDONLY) = 13
...
nanosleep(,
Size: 30720 kB
Rss: 44 kB
Shared_Clean: 0 kB
Shared_Dirty: 0 kB
Private_Clean: 0 kB
Private_Dirty: 44 kB
可以很容易地發現起始地址0xb5ce7000與上麵mmap係統內核調用返回的地址一樣。該塊內存是可讀寫rw,並與其它進程共享s。而/dev/zero則是映射文件,該文件節點是633695。其中,size表示進程可以使用的內存空間,而rss則表示實際分配的內存空間,且由Private_Dirty可以看出,實際分配的44kb內存是由當前進程自己分配的。
apc.num_files_hint = 1000
apc.user_entries_hint = 4096
這二配置指定apc可以有多少個緩存條目。apc.num_files_hint說明你估計可能會有多少個文件相應的opcodes需要被緩成,即大約可以有多少個apc_compiler_cache條目。另外apc.user_entries_hint則說明你估計可能會有多少個apc_userdata_cache條目需要被緩存。如果項目中不使用apc_store()緩存用戶數據的話,該值可以設定得更小。也就是說apc.num_files_hint與apc.user_entries_hint之和決定了APC允許最大緩存對象條目的數量。準確地設置這二個值可以得到最佳查詢性能。當然,如果你不清楚要進行多少緩存(緩存對象實例)的情況下,你可以不必修改這二項配置。其中apc.user_entries_hint要根據項目實際開發使用了apc_store()條目估計其值大小。相較而言,apc.num_files_hint可以通過find命令,更容易地估計其大小。比如我們的web根目是/var/vhosts,則使用下麵的find命令可以大致地統計當前apc.num_files_hint數目.
#find /var/vhosts \( -name “*.php” -or -name “*.inc” \) -type f -print |wc -l
1442
apc.stat = 1
apc.stat_ctime = 0
這二個參數,隻跟apc_compiler_cache緩存相關,並不影響apc_user_cache。我們前麵提到過apc_complier_cache,它緩存的對象是php源文件一一對應的opcodes(目標文件)。PHP源文件存放在磁盤設備上,與之相對應的Opcodes目標文件位置內存空間(共享內存),那麼當php源文件被修改以後,怎麼通知更新內存空間的opcodes呢?每次接收到請求後,APC都會去檢查打開的php源文件的最後修改時間,如果文件的最後修改時間與相應的內存空間緩存對象記錄的最後修改時間不一致的話,APC則會認為存放在內存空間的Opcode目標文件(緩存對象)已經過期了,acp會將緩存對象清除並且保存新解析得到的Opcode。我們關心的是,即便沒有更新任何php源文件,每次接受到http請求後,APC都會請求係統內核調用stat()來獲取php源文件最後修改時。我們可以通過將apc.stat設置為0,要求APC不去檢查Opcodes相對應的php源文件是否更新了。這樣可以獲得最佳的性能,我們也推薦這麼做。不過,這樣做有一點不好的就是,一旦有PHP源文件更新了之後,需要重啟httpd守護進程或者調用apc_cache_clear()函數清空APC緩存來保證php源文件與緩存在內存空間的Opcodes相一致。
define('ROOTP', dirname(__FILE__) . '/');
include(ROOTP . 'i1.php');
require(ROOTP . 'i2.php');
include_once(ROOTP . 'i3.php');
require_once(ROOTP . 'i4.php');
require(ROOTP . 'i5.php');
include(ROOTP . 'i6.php');
?>
# strace -e trace=file -p `cat /var/run/httpd.pid`
getcwd("/var/www/html", 4096) = 14
lstat64("/var", ) = 0
open("/var/www/html/i3.php", O_RDONLY) = 12
lstat64("/var", ) = 0
open("/var/www/html/i4.php", O_RDONLY) = 12
chdir("/tmp") = 0
# strace -e trace=file -p `cat /var/run/httpd.pid`
getcwd("/var/www/html", 4096) = 14
open("/var/www/html/i3.php", O_RDONLY) = 12
open("/var/www/html/i4.php", O_RDONLY) = 12
chdir("/tmp") = 0
對比可見,當apc.stat=0時,省了很多係統內核調用,我們沒有看到係統內核調用stat64了。其中,i3.php和i4.php分別是php的include_once和require_once函數調用,它要交給fstat()係統內核調用來檢查文件是否打開過。單從性能角度出發的話,require比require_once性能更佳。
設置apc.stat_ctime的意義並是很大。如果apc.stat_ctime值為1時,僅當php源文件的創建時間(ctime)大於php源文件的最後修改時間(mtime)時,緩存對象的mtime時間會被php源文件的ctime所代替,否則緩存對象的mtime依然記錄為php源文件的mtime。這樣做是防止通過cvs, svn或者rsync等工具刷新php源文件的mtime,這樣會導致APC通過比對php源文件的創建時間ctime來決定緩存對象有沒有過期。我們推薦該保持默認值,即apc.stat_ctime = 0
apc.ttl=0
apc.user_ttl=0
緩存對象的生命周期。其中ttl表示Time To Live,意味著指定時間後緩存對象會被清除。其中0表示永不過期。我們前麵提過,APC能緩存的條目是受限定的,如果你把ttl設置永不過期的話,當緩存條目已滿或者緩存空間不夠,之後的緩存都將失敗。其中apc.ttl作用於apc_compiler_cache。當apc.ttl大於0時,每次請求都會對比這次的請求時間與上一次請求時間之差是不是大於apc.ttl,如果大於apc.ttl,則會被認緩存條目過期了,會被清理。比較有意思的是apc.user_ttl,它主要作用於apc_user_cache緩存。我們知道,這種類型的緩存是通過apc_store($key, $var, $ttl = 0)創建的緩存對象。函數apc_store()中指定的$ttl與php.ini中設定的apc.user_ttl有什麼異同,是我們比較關心的。因為它們同樣作用於apc_userdata_cache緩存。經過分析,我們知道:判斷apc_user_cache緩存過期的依據是,當apc.user_ttl大於0,且這次http請求時間與上一次http請求時間之差大於apc.user_ttl,則認為相應的緩存條目已過期;或者,user.data.ttl(php函數apc_store()中指定的$ttl)大於0,且這次http請求時間與緩存對象創建時間ctime之差大於user.data.ttl,則同樣認為緩存條目已過期,會被清除。我們推薦,如果你的項目較為穩定,並且apc.stat設置為0。同時apc.shm_size、apc.num_files_hint設置合理的話,apc.ttl建議設置為0。即apc_compiler_cache永不回收,直到重啟httpd守護進程或者調用函數apc_cache_clear()清緩存。至於apc.user_ttl,建議設置為0,由開發人員調用apc_store()函數的時候,設置$ttl來指定該緩存對象的生命周期。
apc.slam_defense=0
apc.write_lock=1
apc.file_update_protection=2
之所以把這三個配置放在一起解釋,是因為他們的意義很相近。其中apc.file_update_protection最好理解,它的單位是時間單位秒。如果當前http請求時間與php源文件最好修改時間mtime之差小於apc.file_update_protection時間,APC則不會緩存該php源文件與之對應的Opcodes,直到接下來的某次訪問,並且訪問時間與php源文件的最後修改時間大於apc.file_update_protection時間,相之相應的Opcodes才會被緩存到共享內存空間。這樣做的好處是,不容易被用戶訪問到你正在修改的源文件。我們推薦在開發環境,該值可以設置得更大一點,但在運營環境,我們推薦保留默認值即可。當你的網站並發量很大的時候,可能出現由http守護進程fork的多個子進程同時緩存同一份Opcodes的情況。通過apc.slam_defense則可以減少這種事情的發生機率。比如,apc.slam_defense值設置為60的時候,當遇到未緩存的Opcodes,每100次有60次是不緩存的。對於並發量不大的網站,我們推薦該值設定為0,對於並發量高的網站我們可以根據統計適當地調整該值。而apc.write_lock是一個布爾值,當該值設置為1的時候,當多個進程同時緩存同一份Opcodes時,僅當最先那個進程緩存有效,其它的無效。通過apc.write_lock設置,有效地避免了緩存寫競爭的出現。
apc.max_file_size=1
Mapc.filters = NULL
apc.cache_by_default=1
這三個配置放在一起,是因為他們都用於限製緩存。其中apc.max_file_size表示如果php源文件超過了1M,則與之對應的opcodes不被緩存。而apc.filters指定一個文件過濾列表,以逗號(,)隔開。當apc.cache_by_default等於1時,與apc.filters列表中指定的文件名相匹配的文件不會被緩存。相反,apc.cache_by_default等於0時,僅緩存與acp.filters列表中指定的文件相匹配的文件。
三、總結
1,使用Spinlocks鎖機製,能夠達到最佳性能。
2,APC提供了apc.php,用於監控與管理APC緩存。不要忘記修改管理員名和密碼
3,APC默認通過mmap匿名映射創建共享內存,緩存對象都存放在這塊”大型”的內存空間。由APC自行管理該共享內存
4,我們需要通過統計調整apc.shm_size、apc.num_files_hints、apc.user_entries_hint的值。直到最佳
5,好吧,我承認apc.stat = 0 可以獲得更佳的性能。要我做什麼都可以接受.
6,PHP預定義常量,可以使用apc_define_constants()函數。不過據APC開發者介紹說pecl hidef性能更佳,拋異define吧,它是低效的。
7,函數apc_store(),對於係統設置等PHP變量,生命周期是整個應用(從httpd守護進程直到httpd守護進程關閉),使用APC比Memcached會更好。必竟不要經過網絡傳輸協議tcp。
8,APC不適於通過函數apc_store()緩存頻繁變更的用戶數據,會出現一些奇異現象。
9.APC模塊的發言。作為APC的作者,他的見解應該是非常有參考價值的。
APC will probably be 20-30% faster, but if you are writing to it frequently it can cause problems. The APC cache is best for things that change very rarely. And by very rarely I mean days, not hours or minutes.Because of the way APC does an anonymous file-backed mmap where I unlink the file at startup to get process-death protection, it isn’t easy to get at the cache from a separate standalone command line script. That can be solved by mmap’ing slightly differently, but in the default config your approach won’t work.-Rasmus
就是說APC不適合用於頻繁寫的場合,你最佳隻用他來保存那種幾天都不會更改的內容。否則出了莫名其妙的問題就不好怎麼解釋了。在以前的應用中,我確實有將apc用在頻繁寫的場合,偶爾會出現內存耗盡,進而引起所有http請求卡死,形式一發不可收拾整個服務器當掉。所以目前我僅僅用apc來緩存opcode的php代碼,不在程式中顯式的調用他,算是相安無事。
四、測試:
使用APC
print_r(apc_cache_info());
?>
Array
(
[num_slots] => 1031
[ttl] => 0
[num_hits] => 4
[num_misses] => 1
[num_inserts] => 1
[expunges] => 0
[mem_size] => 4240
[num_entries] => 1
[file_upload_progress] => 1
[memory_type] => mmap
[locking_type] => spin Locks
[cache_list] => Array
(
[0] => Array
(
[type] => file
[device] => 2049
[inode] => 883230
[filename] => /var/www/tb.php
[num_hits] => 4
[deletion_time] => 0
[ref_count] => 1
[mem_size] => 4240
)
)
[deleted_list] => Array
(
)
[slot_distribution] => Array
(多次點擊,可以發現num_hits在變化,說明緩存命中了!
最後更新:2017-10-08 18:01:26