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=1 apc.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模組初始化的分配的記憶體空間。
<?php //@file: apc_load.php if (!extension_loaded('apc')) { dl('apc.so'); #載入apc.so模組 echo posix_getpid(); #//輸出當前進程的pid,我這裡這裡輸出的是14735 ob_flush(); flush(); sleep(3600); #讓進程進入休眠狀態.這樣,我們可以觀察記憶體配置情況 } ?>
#strace -p `cat /var/run/httpd.pid` open("/var/www/html/apc_load.php", O_RDONLY) = 13 ... mmap2(NULL, 31457280, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) = 0xb5ce7000 ... nanosleep({3600, 0}, 紅色部分,我們可以看出。通過mmap系統核心調用分配了30M(31457280/1024/1024)記憶體空間。PROT_READ|PROT_WRITE表示該記憶體空間可供讀取與寫入。MAP_SHARED表示該記憶體空間與其它進程是共用的,即其它進程也可以進行讀取與寫入,我們可以通過apc.php進行管理該塊記憶體空間亦是受益於此設定。MAP_ANONYMOUS則表示匿名映射。其中fd=-1表示忽略,因為這裡映射的特殊裝置/dev/zero。最後的0表示無位移量。我們還可以通過進程映像檔案查看該塊記憶體的具體情況 #cat /proc/14735/smaps b5ce7000-b7ae7000 rw-s 00000000 00:08 633695 /dev/zero (deleted) 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相一致。
<?php 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 stat64("/var/www/html/i1.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 stat64("/var/www/html/i2.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/i3.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 open("/var/www/html/i3.php", O_RDONLY) = 12 stat64("/var/www/html/i3.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 lstat64("/var", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 lstat64("/var/www/html/i4.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 open("/var/www/html/i4.php", O_RDONLY) = 12 stat64("/var/www/html/i4.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 stat64("/var/www/html/i5.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 stat64("/var/www/html/i6.php", {st_mode=S_IFREG|0644, st_size=39, ...}) = 0 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=1M apc.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代碼,不在程式中顯式的調用他,算是相安無事。 |