日請求億級的 QQ 會員 AMS 平臺 PHP7 升級實踐

QQ會員活動運營平臺(AMS),是QQ會員增值運營業務的重要載體之一,承擔海量活動運營的Web系統。AMS是一個主要採用PHP語言實現的活動運營平臺, CGI日請求3億左右,高峯期達到8億。然而,在以前比較長的一段時間裏,咱們都採用了比較老舊的基礎軟件版本,就是PHP5.2+Apache2.0(2008年的技術)。尤爲從去年開始,隨着AMS業務隨着QQ會員增值業務的快速增加,性能壓力日益變大。php

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

因而,自2015年5月,咱們就開始規劃PHP底層升級,最終的目標是升級到PHP7。那時,PHP7尚處於研發階段,而咱們討論和預研就已經開始了。html

1、PHP7的學習和預研

1. HHVM和JITgit

2015年就PHP性能優化的方案,有另一個比較重要的角色,就是由Facebook開源的HHVM(HipHop Virtual Machine,HHVM是一個Facebook開源的PHP虛擬機)。HHVM使用JIT(Just In Time,即時編譯是種軟件優化技術,指在運行時纔會去編譯字節碼爲機器碼)的編譯方式以及其餘技術,讓PHP代碼的執行性能大幅提高。據傳,能夠將PHP5版本的原生PHP代碼提高5-10倍的執行性能。github

HHVM起源於Facebook公司,Facebook早起的不少代碼是使用PHP來開發的,可是,隨着業務的快速發展,PHP執行效率成爲愈來愈明顯的問題。爲了優化執行效率,Facebook在2008年就開始使用HipHop,這是一種PHP執行引擎,最初是爲了將 Fackbook的大量PHP代碼轉成 C++,以提升性能和節約資源。使用HipHop的PHP代碼在性能上有數倍的提高。後來,Facebook將HipHop平臺開源,逐漸發展爲如今的 HHVM。web

HHVM成爲一個PHP性能優化解決方案時,PHP7還處於研發階段。曾經看過部分同窗對於HHVM的交流,性能能夠得到可觀的提高,可是服務運維和PHP語法兼容有必定成本。有一陣子,JIT成爲一個呼聲很高的東西,不少技術同窗建議PHP7也應該經過JIT來優化性能。shell

2015年7月,我參加了中國PHPCON,聽了惠新宸關於PHP7內核的技術分享。實際上,在2013年的時候,惠新宸(PHP7內核開發者)和Dmitry(另外一位PHP語言內核開發者之一)就曾經在PHP5.5的版本上作過一個JIT的嘗試(並無發佈)。PHP5.5的原來的執行流程,是將PHP代碼經過詞法和語法分析,編譯成opcode字節碼(格式和彙編有點像),而後,Zend引擎讀取這些opcode指令,逐條解析執行。apache

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

而他們在opcode環節後引入了類型推斷(TypeInf),而後經過JIT生成ByteCodes,而後再執行。api

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

因而,在benchmark(測試程序)中獲得很是好的結果,實現JIT後性能比PHP5.5提高了8倍。然而,當他們把這個優化放入到實際的項目WordPress(一個開源博客項目)中,卻幾乎看不見性能的提高。緣由在於測試項目的代碼量比較少,經過JIT產生的機器碼也不大,而真實的WordPress項目生成的機器碼太大,引發CPU緩存命中率降低(CPU Cache Miss)。數組

總而言之,JIT並不是在每一個場景下都是點石成金的利器,而脫離業務場景的性能測試結果,並不必定具備表明性。緩存

從官方放出Wordpress的PHP7和HHVM的性能對比能夠看出,二者基本處於同一水平。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

2. PHP7在性能方面的優化

PHP7是一個比較底層升級,比起PHP5.6的變化比較大,而就性能優化層面,大體能夠彙總以下:

(1)將基礎變量從struct(結構體)變爲union(聯合體),節省內存空間,間接減小CPU在內存分配和管理上的開銷。

(2)部分基礎變量(zend_array、zend_string等)採用內存空間連續分配的方式,下降CPU Cache Miss的發生的機率。CPU從CPU Cache獲取數據和從內存獲取,它們之間效率相差能夠高達100倍。舉一個近似的例子,系統從內存讀取數據和從磁盤讀取數據的效率差異很大,CPU Cache Miss相似遇到缺頁中斷。

(3)經過宏定義和內聯函數(inline),讓編譯器提早完成部分工做。無需在程序運行時分配內存,可以實現相似函數的功能,卻沒有函數調用的壓棧、彈棧開銷,效率會比較高。

… …

3. AMS平臺技術選型的背景

就提高PHP的性能而言,能夠選擇的是2015年就可直接使用的HHVM或者是2015年末才發佈正式版的PHP7。會員AMS是一個訪問量級比較大的一個Web系統,通過四年持續的升級和優化,積累了800多個業務功能組件,還有各類PHP編寫的公共基礎庫和腳本,代碼規模也比較大。

咱們對於PHP版本對代碼的向下兼容的需求是比較高的,所以,就咱們業務場景而言,PHP7良好的語法向下兼容,正是咱們所須要的。所以,咱們選擇以PHP7爲升級的方案。

2、PHP7升級面臨的風險和挑戰

對於一個已經現網在線的大型公共Web服務來講,基礎公共軟件升級,一般是一件吃力不討好的工做,作得好,不必定被你們感知到,可是,升級出了問題,則須要承擔比較重的責任。爲了儘可能減小升級的風險,咱們必須先弄清楚咱們的升級存在挑戰和風險。

因而,咱們整理了升級挑戰和風險列表:

(1)Apache2.0和PHP5.2這兩個2008-2009年的基礎軟件版本比較古老,升級到Apache2.4和PHP7,版本升級跨度比較大,時間跨度相差7-8年,所以,兼容性問題挑戰比較高。實際上,咱們公司的現網PHP服務,不少都停留在PHP5.2和PHP5.3的版本,版本偏低。

(2)AMS大量使用自研tphplib擴展,tphplib很早在公司內部就沒有人維護了,這個擴展以前只有PHP5.3和PHP5.2的編譯so版本,而且,部分擴展沒有支持線程安全。支持線程安全,是由於咱們之前的Apache使用了prefork模式,而咱們但願可以使用Apache2.4的Event模式(2014年中,在prefork和worker以後,推出的多進程線程管理模式,對於支持高併發,有更良好的表現)。

(3)語法兼容性問題,從PHP5.2到PHP7的跨度過大,即便PHP官方號稱在向下兼容方面作到99%,可是,咱們的代碼規模比較大,它仍然是一個未知的風險。

(4)新軟件面臨的風險,將Apache和PHP這種基礎軟件升級到最新的版本,而這些版本的部分功能可能存在未知的風險和缺陷。

部分同窗可能會建議採用Nginx會是更優的選擇,的確,單純比較Nginx和Apache在高併發方面的性能,Nginx的表現更優。可是就PHP的CGI而言,Nginx+php-ftpm和Apache+mod_php二者並無很大的差距。另外一方面,咱們由於長期使用Apache,在技術熟悉和經驗方面積累更多,所以,它可能不是最佳的選擇,可是,具體到咱們業務場景,算是比較合適的一個選擇。

3、版本升級實施過程

1. 高跨度版本升級方式

從一個2008年的Apache2.0直接升級到2016年的Apache2.4,這個跨度過於大,甚至使用的http.conf的配置文件都有不少的不一樣,這裏的須要更新的地方比較多,未知的風險也是存在的。因而,咱們的作法,是先嚐試將Apache2.0升級到Apach2.2,調整配置、觀察穩定性,而後再進一步嘗試到Apach2.4。所幸的是,Apache(httpd)是一個比較特別的開源社區,他們以前一直同時維護這兩個分支版本的Apache(2.2和2.4),所以,即便是Apache2.2也有比較新的版本。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

因而,咱們先升級了一個PHP5.2+Apache2.2,對兼容性進行了測試和觀察,確認二者之間是能夠比較平滑升級後,咱們開始進行Apache2.4的升級方案。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

PHP5.2的升級,咱們也採用相同的思路,咱們先將PHP5.2升級至PHP5.6(當時,PHP7仍是beta版本),而後再將PHP5.6升級到PHP7,以更平滑的方式,逐步解決不一樣的問題。

因而,咱們的升級計劃變爲:

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

Apache2.4編譯爲動態MPM的模式(支持經過httpd配置切換prefork/worker/event模式),根據現網風險等實時降級。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

Prefork、Worker、Event三者粗略介紹:

(1) prefork,多進程模式,1個進程服務於1個用戶請求,成本比較高。可是,穩定性最高,不須要支持線程安全。

(2) worker,多進程多線程模式,1個進程含有多個worker線程,1個worker線程服務於1個用戶請求,由於線程更輕量,成本比較低。可是,在KeepAlive場景下,worker資源會被client佔據,沒法響應其餘請求(空等待)。

(3) event,多進程多線程模式,1個進程也含有多個worker線程,1個worker線程服務於1個用戶請求。可是,它解決了KeepAlive場景下的worker線程被佔據問題,它經過專門的線程來管理這些KeepAlive鏈接,而後再分配「工做」給具體處理的worker,工做worker不會由於KeepAlive而致使空等待。

關於Event模式的官方介紹:

http://httpd.apache.org/docs/2.4/mod/event.html

(部分同窗可能會有event模式不支持https的印象,那個說法實際上是2年多之前的國內部分技術博客的說法,目前的版本是支持的,詳情能夠瀏覽官方介紹)

開啓動態切換模式的方法,就是在編譯httpd的時候加上:

–enable-mpms-shared=all

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者
 

從PHP5.2升級到PHP5.6相對比較容易,咱們主要的工做以下:

(1)清理了部分再也不使用的老擴展

(2)解決掉線程安全問題

(3)將cmem等api編譯到新的版本

(4)PHP代碼語法基於PHP5.6的兼容(實際上變化不大)

(5)部分擴展的同步調整。apc擴展變爲zend_opcache和apcu,之前的apc是包含了編譯緩存和用戶內存操做的功能,在PHP比較新版本里,被分解爲獨立的兩個擴展。

從PHP5.6升級到PHP7.0的工做量就比較多,也相對比較複雜,所以,咱們制定了每個階段的升級計劃:

(1)技術預研,PHP7升級準備。

(2)環境編譯和搭建,下載相關的編譯包,搭建完整的編譯環境和測試環境。(編譯環境仍是須要比較多的依賴so)

(3)兼容升級和測試。PHP7擴展的從新編譯和代碼兼容性工做,AMS功能驗證,性能壓測。

(4)線上灰度。打包爲pkg的安裝包,編寫相關的安裝shell安裝執行代碼(包括軟連接、解決一些so依賴)。而後,灰度安裝到現網,觀察。

(5)正式發佈。擴大灰度範圍,全量升級。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

由於從PHP5.2升級到PHP5.6的過程當中,不少問題已經被咱們提早解決了,因此,PHP7的升級主要難點在於tphplib擴展的編譯升級。

涉及主要的工做包括:

(1)PHP5.6的擴展到PHP7.0的比較大幅度改造升級(工做量比較大的地方)

(2)兼容apcu的內存操做函數的更名。PHP5的時候,咱們使用的apc前綴的函數不可用了,同步變爲apcu前綴的函數(須要apcu擴展)。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

(3)語法兼容升級。實際上工做量不算大,從PHP5.6升級到PHP7變化並很少。

咱們大概在2016年4月中旬份完成了PHP7和Apache的編譯工做, 4月下旬進行現網灰度,5月初全量發佈到其中一個現網集羣。

2. 升級過程當中的錯誤調試方法

在升級和從新編譯PHP7擴展時,若是執行結果不符合預期或者進程core掉,不少錯誤都是沒法從error日誌裏看見的,不利於分析問題。能夠採用如下幾種方法,能夠用來定位和分析大部分的問題:

(1) var_dump/exit

從PHP代碼層逐步輸出信息和執行exit,能夠逐步定位到異常執行的PHP函數位置,而後再根據PHP函數名,反查擴展內的實現函數,找到問題。這種方法比較簡單,可是效率不高。

(2) gdb –p/gdb c

這種方法主要用於分析進程core的場景,咱們採用的編譯方式,是將mod_php(PHP變成Apache的子或塊的方式),使用gdb –p來監控Apache的服務進程。

命令:ps aux|grep httpd

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

gdb調試指定進程:

命令:gdb -p

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

使用c進行捕獲,而後構造可以致使core的web請求:

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

Apache一般是多進程模式,爲了讓問題比較容易復現,能夠在http.con裏修改參數,將啓動進程數修改成1個(下圖中的多個參數都須要調整,以達到只啓動單進程單線程的目的)。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

固然還有一種更簡單的方法,由於Apache自己就支持單進程調試模式的。

./apachectl -k start -X -e debug

而後再經過gdb –p來調試就更簡單一些。

(3)經過strace命令查看Apache進程具體在作了些什麼事情,根據裏面的執行內容,分析和定位問題。

strace -Ttt -v -s1024 -f -p pid(進程id)

備註:執行這些命令,注意權限問題,極可能須要root權限。

4、PHP5.6到PHP7.0擴展升級實踐記錄

1. 數據類型的變化

(1)zval

php7的誕生始於zval結構的變化,PHP7再也不須要指針的指針,絕大部分zval**須要修改爲zval*。若是PHP7直接操做zval,那麼zval*也須要改爲zval,Z_*P()也要改爲Z_*(),ZVAL_*(var, …)須要改爲ZVAL_*(&var, …),必定要謹慎使用&符號,由於PHP7幾乎不要求使用zval*,那麼不少地方的&也是要去掉的。

ALLOC_ZVAL,ALLOC_INIT_ZVAL,MAKE_STD_ZVAL這幾個分配內存的宏已經被移除了。大多數狀況下,zval*應該修改成zval,而INIT_PZVAL宏也被移除了。

 

(2)整型

直接切換便可:

(3)字符串類型

PHP5.6版本中使用 char* + len的方式表示字符串,PHP7.0中作了封裝,定義了zend_string類型:

zend_string和char*的轉換:

擴展方法,解析參數時,使用字符串的地方,將‘s’替換成‘S’:

(4)自定義對象

源代碼:

zend_object是一個可變長度的結構。所以在自定義對象的結構中,zend_object須要放在最後一項:

 

(5)數組

7.0中的hash表定義以下,給出了一些註釋:

 

其中,PHP7在zend_hash.h中定義了一系列宏,用來操做數組,包括遍歷key、遍歷value、遍歷key-value等,下面是一個簡單例子:

 

PHP5.6版本中是經過zend_hash_find查找key,而後將結果給到zval **變量,而且查詢不到時須要本身分配內存,初始化一個item,設置默認值。

2. PHP7中的api變化

(1)duplicate參數

PHP5.6中不少API中都須要填入一個duplicate參數,代表一個變量是否須要複製一份,尤爲是string類的操做,PHP7.0中取消duplicate參數,對於string相關操做,只要有duplicate參數,直接刪掉便可。由於PHP7.0中定義了zval_string結構,對字符串的操做,再也不須要duplicate值,底層直接使用zend_string_init初始化一個zend_string便可,而在PHP5.6中string是存放在zval中的,而zval的內存須要手動分配。

涉及的API彙總以下:

add_index_string、add_index_stringl、add_assoc_string_ex、add_assoc_stringl_ex、add_assoc_string、add_assoc_stringl、add_next_index_string、add_next_index_stringl、add_get_assoc_string_ex、add_get_assoc_stringl_ex、add_get_assoc_string、add_get_assoc_stringl、add_get_index_string、add_get_index_stringl、add_property_string_ex、add_property_stringl_ex、add_property_string、add_property_stringl、ZVAL_STRING、ZVAL_STRINGL、RETVAL_STRING、RETVAL_STRINGL、RETURN_STRING、RETURN_STRINGL

(2)MAKE_STD_ZVAL

PHP5.6中,zval變量是在堆上分配的,建立一個zval變量須要先聲明一個指針,而後使用MAKE_STD_ZVAL進行分配空間。PHP7.0中,這個宏已經取消,變量在棧上分配,直接定義一個變量便可,再也不須要MAKE_STD_ZVAL,使用到的地方,直接去掉就好。

(3)ZEND_RSRC_DTOR_FUNC

修改參數名rsrc爲res

 

PHP7.0中,將zend_rsrc_list_entry結構升級爲zend_resource,在新版本中只須要修改一下參數名稱便可。

(4)二級指針宏,即Z_*_PP

PHP7.0中取消了全部的PP宏,大部分狀況直接使用對應的P宏便可。

(5)zend_object_store_get_object被取消

根據官方wiki,能夠定義以下宏,用來獲取object,實際狀況看,這個宏用的仍是比較頻繁的:

 

(6)zend_hash_exists、zend_hash_find

對全部須要字符串參數的函數,PHP5.6中的方式是傳遞兩個參數(char* + len),而PHP7.0中定義了zend_string,所以只須要一個zend_string變量便可。

返回值變成了zend_bool類型:

 

參考資料:

一、php5 to phpng,http://yaoguais.com/?s=md/php/php7-vm.md
二、PHP擴展開發及內核應用, http://www.walu.cc/phpbook/10.1.md
三、PHP 7中新的Hashtable實現和性能改進 ,http://gywbd.github.io/posts/2014/12/php7-new-hashtable-implementation.html
四、深刻理解PHP7之zval, https://github.com/laruence/php7-internal/blob/master/zval.md
五、官方wiki, https://wiki.php.net/phpng-upgrading
六、php手冊 ,http://php.net/manual/zh/index.php
七、PHP7 使用資源包裹第三方擴展的實現及其源碼解讀 ,https://mengkang.net/684.html

5、AMS平臺升級PHP7的性能優化成果

現網服務是一個很是重要而又敏感的環境,輕則影響用戶體驗,重則產生現網事故。所以,咱們4月下旬完成PHP7編譯和測試工做以後,就在AMS其中一臺機器進行了灰度上線,觀察了幾天後,而後逐步擴大灰度範圍,在5月初完成升級。

這個是咱們壓測AMS一個查詢多個活動計數器的壓測結果,以及現網CGI機器,在高峯相同TGW流量場景下的CPU負載數據:

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

就咱們的業務壓測和現網結果來看,和官方所說的性能提高一倍,基本一致。

日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者
日請求億級的QQ會員AMS平臺PHP7升級實踐 - 徐漢彬Hansion - 技術行者

AMS平臺擁有很多的CGI機器,PHP7的升級和應用給咱們帶來了性能的提高,能夠有效節省硬件資源成本。而且,經過Apache2.4的Event模式,咱們也加強了Apache在支持併發方面的能力。

6、小結

咱們PHP7升級研發項目組,在過去比較長的一個時間段裏,通過持續地努力和推動,終於在2016年4月下旬現網灰度,5月初在集羣中全量升級,爲咱們的AMS活動運營平臺帶來性能上大幅度的提高。

PHP7的革新,對於PHP語言自己而言,具備非凡的意義和價值,這讓我更加確信一點,PHP會是一個愈來愈好的語言。同時,感謝PHP社區的開發者們,爲咱們業務帶來的性能提高。

相關文章
相關標籤/搜索