溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何進(jìn)行QQ會(huì)員AMS平臺(tái)PHP7升級(jí)

發(fā)布時(shí)間:2021-10-11 11:30:29 來(lái)源:億速云 閱讀:110 作者:柒染 欄目:云計(jì)算

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)如何進(jìn)行QQ會(huì)員AMS平臺(tái)PHP7升級(jí),文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

QQ會(huì)員活動(dòng)運(yùn)營(yíng)平臺(tái)(AMS),是QQ會(huì)員增值運(yùn)營(yíng)業(yè)務(wù)的重要載體之一,承擔(dān)海量活動(dòng)運(yùn)營(yíng)的Web系統(tǒng)。AMS是一個(gè)主要采用PHP語(yǔ)言實(shí)現(xiàn)的活動(dòng)運(yùn)營(yíng)平臺(tái), CGI日請(qǐng)求3億左右,高峰期達(dá)到8億。然而,在之前比較長(zhǎng)的一段時(shí)間里,我們都采用了比較老舊的基礎(chǔ)軟件版本,就是PHP5.2+Apache2.0(2008年的技術(shù))。尤其從去年開始,隨著AMS業(yè)務(wù)隨著QQ會(huì)員增值業(yè)務(wù)的快速增長(zhǎng),性能壓力日益變大。

于是,自2015年5月,我們就開始規(guī)劃PHP底層升級(jí),最終的目標(biāo)是升級(jí)到PHP7。那時(shí),PHP7尚處于研發(fā)階段,而我們討論和預(yù)研就已經(jīng)開始了。

一.PHP7的學(xué)習(xí)和預(yù)研

1. HHVM和JIT

2015年就PHP性能優(yōu)化的方案,有另外一個(gè)比較重要的角色,就是由Facebook開源的HHVM(HipHop Virtual Machine,HHVM是一個(gè)Facebook開源的PHP虛擬機(jī))。HHVM使用JIT(Just In Time,即時(shí)編譯是種軟件優(yōu)化技術(shù),指在運(yùn)行時(shí)才會(huì)去編譯字節(jié)碼為機(jī)器碼)的編譯方式以及其他技術(shù),讓PHP代碼的執(zhí)行性能大幅提升。據(jù)傳,可以將PHP5版本的原生PHP代碼提升5-10倍的執(zhí)行性能。

HHVM起源于Facebook公司,F(xiàn)acebook早起的很多代碼是使用PHP來(lái)開發(fā)的,但是,隨著業(yè)務(wù)的快速發(fā)展,PHP執(zhí)行效率成為越來(lái)越明顯的問(wèn)題。為了優(yōu)化執(zhí)行效率,F(xiàn)acebook在2008年就開始使用HipHop,這是一種PHP執(zhí)行引擎,最初是為了將 Fackbook的大量PHP代碼轉(zhuǎn)成 C++,以提高性能和節(jié)約資源。使用HipHop的PHP代碼在性能上有數(shù)倍的提升。后來(lái),F(xiàn)acebook將HipHop平臺(tái)開源,逐漸發(fā)展為現(xiàn)在的 HHVM。

HHVM成為一個(gè)PHP性能優(yōu)化解決方案時(shí),PHP7還處于研發(fā)階段。曾經(jīng)看過(guò)部分同學(xué)對(duì)于HHVM的交流,性能可以獲得可觀的提升,但是服務(wù)運(yùn)維和PHP語(yǔ)法兼容有一定成本。有一陣子,JIT成為一個(gè)呼聲很高的東西,很多技術(shù)同學(xué)建議PHP7也應(yīng)該通過(guò)JIT來(lái)優(yōu)化性能。

2015年7月,我參加了中國(guó)PHPCON,聽(tīng)了惠新宸關(guān)于PHP7內(nèi)核的技術(shù)分享。實(shí)際上,在2013年的時(shí)候,惠新宸(PHP7內(nèi)核開發(fā)者)和Dmitry(另一位PHP語(yǔ)言內(nèi)核開發(fā)者之一)就曾經(jīng)在PHP5.5的版本上做過(guò)一個(gè)JIT的嘗試(并沒(méi)有發(fā)布)。PHP5.5的原來(lái)的執(zhí)行流程,是將PHP代碼通過(guò)詞法和語(yǔ)法分析,編譯成opcode字節(jié)碼(格式和匯編有點(diǎn)像),然后,Zend引擎讀取這些opcode指令,逐條解析執(zhí)行。

而他們?cè)趏pcode環(huán)節(jié)后引入了類型推斷(TypeInf),然后通過(guò)JIT生成ByteCodes,然后再執(zhí)行。

于是,在benchmark(測(cè)試程序)中得到非常好的結(jié)果,實(shí)現(xiàn)JIT后性能比PHP5.5提升了8倍。然而,當(dāng)他們把這個(gè)優(yōu)化放入到實(shí)際的項(xiàng)目WordPress(一個(gè)開源博客項(xiàng)目)中,卻幾乎看不見(jiàn)性能的提升。原因在于測(cè)試項(xiàng)目的代碼量比較少,通過(guò)JIT產(chǎn)生的機(jī)器碼也不大,而真實(shí)的WordPress項(xiàng)目生成的機(jī)器碼太大,引起CPU緩存命中率下降(CPU Cache Miss)。

總而言之,JIT并非在每個(gè)場(chǎng)景下都是點(diǎn)石成金的利器,而脫離業(yè)務(wù)場(chǎng)景的性能測(cè)試結(jié)果,并不一定具有代表性。

從官方放出Wordpress的PHP7和HHVM的性能對(duì)比可以看出,兩者基本處于同一水平。

2. PHP7在性能方面的優(yōu)化

PHP7是一個(gè)比較底層升級(jí),比起PHP5.6的變化比較大,而就性能優(yōu)化層面,大致可以匯總?cè)缦拢?/p>

  • 將基礎(chǔ)變量從struct(結(jié)構(gòu)體)變?yōu)閡nion(聯(lián)合體),節(jié)省內(nèi)存空間,間接減少CPU在內(nèi)存分配和管理上的開銷。

  • 部分基礎(chǔ)變量(zend_array、zend_string等)采用內(nèi)存空間連續(xù)分配的方式,降低CPU Cache Miss的發(fā)生的概率。CPU從CPU Cache獲取數(shù)據(jù)和從內(nèi)存獲取,它們之間效率相差可以高達(dá)100倍。舉一個(gè)近似的例子,系統(tǒng)從內(nèi)存讀取數(shù)據(jù)和從磁盤讀取數(shù)據(jù)的效率差別很大,CPU Cache Miss類似遇到缺頁(yè)中斷。

  • 通過(guò)宏定義和內(nèi)聯(lián)函數(shù)(inline),讓編譯器提前完成部分工作。無(wú)需在程序運(yùn)行時(shí)分配內(nèi)存,能夠?qū)崿F(xiàn)類似函數(shù)的功能,卻沒(méi)有函數(shù)調(diào)用的壓棧、彈棧開銷,效率會(huì)比較高。
    ... ...

3. AMS平臺(tái)技術(shù)選型的背景

就提升PHP的性能而言,可以選擇的是2015年就可直接使用的HHVM或者是2015年底才發(fā)布正式版的PHP7。會(huì)員AMS是一個(gè)訪問(wèn)量級(jí)比較大的一個(gè)Web系統(tǒng),經(jīng)過(guò)四年持續(xù)的升級(jí)和優(yōu)化,積累了800多個(gè)業(yè)務(wù)功能組件,還有各種PHP編寫的公共基礎(chǔ)庫(kù)和腳本,代碼規(guī)模也比較大。
我們對(duì)于PHP版本對(duì)代碼的向下兼容的需求是比較高的,因此,就我們業(yè)務(wù)場(chǎng)景而言,PHP7良好的語(yǔ)法向下兼容,正是我們所需要的。因此,我們選擇以PHP7為升級(jí)的方案。

二.PHP7升級(jí)面臨的風(fēng)險(xiǎn)和挑戰(zhàn)

對(duì)于一個(gè)已經(jīng)現(xiàn)網(wǎng)在線的大型公共Web服務(wù)來(lái)說(shuō),基礎(chǔ)公共軟件升級(jí),通常是一件吃力不討好的工作,做得好,不一定被大家感知到,但是,升級(jí)出了問(wèn)題,則需要承擔(dān)比較重的責(zé)任。為了盡量減少升級(jí)的風(fēng)險(xiǎn),我們必須先弄清楚我們的升級(jí)存在挑戰(zhàn)和風(fēng)險(xiǎn)。
于是,我們整理了升級(jí)挑戰(zhàn)和風(fēng)險(xiǎn)列表:

  • Apache2.0和PHP5.2這兩個(gè)2008-2009年的基礎(chǔ)軟件版本比較古老,升級(jí)到Apache2.4和PHP7,版本升級(jí)跨度比較大,時(shí)間跨度相差7-8年,因此,兼容性問(wèn)題挑戰(zhàn)比較高。實(shí)際上,我們公司的現(xiàn)網(wǎng)PHP服務(wù),很多都停留在PHP5.2和PHP5.3的版本,版本偏低。

  • AMS大量使用自研tphplib擴(kuò)展,tphplib很早在公司內(nèi)部就沒(méi)有人維護(hù)了,這個(gè)擴(kuò)展之前只有PHP5.3和PHP5.2的編譯so版本,并且,部分?jǐn)U展沒(méi)有支持線程安全。支持線程安全,是因?yàn)槲覀円郧暗腁pache使用了prefork模式,而我們希望能夠使用Apache2.4的Event模式(2014年中,在prefork和worker之后,推出的多進(jìn)程線程管理模式,對(duì)于支持高并發(fā),有更良好的表現(xiàn))。

  • 語(yǔ)法兼容性問(wèn)題,從PHP5.2到PHP7的跨度過(guò)大,即使PHP官方號(hào)稱在向下兼容方面做到99%,但是,我們的代碼規(guī)模比較大,它仍然是一個(gè)未知的風(fēng)險(xiǎn)。

  • 新軟件面臨的風(fēng)險(xiǎn),將Apache和PHP這種基礎(chǔ)軟件升級(jí)到最新的版本,而這些版本的部分功能可能存在未知的風(fēng)險(xiǎn)和缺陷。

部分同學(xué)可能會(huì)建議采用Nginx會(huì)是更優(yōu)的選擇,的確,單純比較Nginx和Apache在高并發(fā)方面的性能,Nginx的表現(xiàn)更優(yōu)。但是就PHP的CGI而言,Nginx+php-ftpm和Apache+mod_php兩者并沒(méi)有很大的差距。另一方面,我們因?yàn)殚L(zhǎng)期使用Apache,在技術(shù)熟悉和經(jīng)驗(yàn)方面積累更多,因此,它可能不是最佳的選擇,但是,具體到我們業(yè)務(wù)場(chǎng)景,算是比較合適的一個(gè)選擇。

三.版本升級(jí)實(shí)施過(guò)程

1. 高跨度版本升級(jí)方式

從一個(gè)2008年的Apache2.0直接升級(jí)到2016年的Apache2.4,這個(gè)跨度過(guò)于大,甚至使用的http.conf的配置文件都有很多的不同,這里的需要更新的地方比較多,未知的風(fēng)險(xiǎn)也是存在的。于是,我們的做法,是先嘗試將Apache2.0升級(jí)到Apach3.2,調(diào)整配置、觀察穩(wěn)定性,然后再進(jìn)一步嘗試到Apach3.4。所幸的是,Apache(httpd)是一個(gè)比較特別的開源社區(qū),他們之前一直同時(shí)維護(hù)這兩個(gè)分支版本的Apache(2.2和2.4),因此,即使是Apache2.2也有比較新的版本。

于是,我們先升級(jí)了一個(gè)PHP5.2+Apache2.2,對(duì)兼容性進(jìn)行了測(cè)試和觀察,確認(rèn)兩者之間是可以比較平滑升級(jí)后,我們開始進(jìn)行Apache2.4的升級(jí)方案。

PHP5.2的升級(jí),我們也采用相同的思路,我們先將PHP5.2升級(jí)至PHP5.6(當(dāng)時(shí),PHP7還是beta版本),然后再將PHP5.6升級(jí)到PHP7,以更平滑的方式,逐步解決不同的問(wèn)題。
于是,我們的升級(jí)計(jì)劃變?yōu)椋?br/>
Apache2.4編譯為動(dòng)態(tài)MPM的模式(支持通過(guò)httpd配置切換prefork/worker/event模式),根據(jù)現(xiàn)網(wǎng)風(fēng)險(xiǎn)等實(shí)時(shí)降級(jí)。

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

  • prefork,多進(jìn)程模式,1個(gè)進(jìn)程服務(wù)于1個(gè)用戶請(qǐng)求,成本比較高。但是,穩(wěn)定性最高,不需要支持線程安全。

  • worker,多進(jìn)程多線程模式,1個(gè)進(jìn)程含有多個(gè)worker線程,1個(gè)worker線程服務(wù)于1個(gè)用戶請(qǐng)求,因?yàn)榫€程更輕量,成本比較低。但是,在KeepAlive場(chǎng)景下,worker資源會(huì)被client占據(jù),無(wú)法響應(yīng)其他請(qǐng)求(空等待)。

  • event,多進(jìn)程多線程模式,1個(gè)進(jìn)程也含有多個(gè)worker線程,1個(gè)worker線程服務(wù)于1個(gè)用戶請(qǐng)求。但是,它解決了KeepAlive場(chǎng)景下的worker線程被占據(jù)問(wèn)題,它通過(guò)專門的線程來(lái)管理這些KeepAlive連接,然后再分配“工作”給具體處理的worker,工作worker不會(huì)因?yàn)镵eepAlive而導(dǎo)致空等待。
    關(guān)于Event模式的官方介紹:
    http://httpd.apache.org/docs/2.4/mod/event.html
    (部分同學(xué)可能會(huì)有event模式不支持https的印象,那個(gè)說(shuō)法其實(shí)是2年多以前的國(guó)內(nèi)部分技術(shù)博客的說(shuō)法,目前的版本是支持的,詳情可以瀏覽官方介紹)

開啟動(dòng)態(tài)切換模式的方法,就是在編譯httpd的時(shí)候加上:
--enable-mpms-shared=all

從PHP5.2升級(jí)到PHP5.6相對(duì)比較容易,我們主要的工作如下:

  • 清理了部分不再使用的老擴(kuò)展

  • 解決掉線程安全問(wèn)題

  • 將cmem等api編譯到新的版本

  • PHP代碼語(yǔ)法基于PHP5.6的兼容(實(shí)際上變化不大)

  • 部分?jǐn)U展的同步調(diào)整。apc擴(kuò)展變?yōu)閦end_opcache和apcu,以前的apc是包含了編譯緩存和用戶內(nèi)存操作的功能,在PHP比較新版本里,被分解為獨(dú)立的兩個(gè)擴(kuò)展。

從PHP5.6升級(jí)到PHP7.0的工作量就比較多,也相對(duì)比較復(fù)雜,因此,我們制定了每一個(gè)階段的升級(jí)計(jì)劃:

  • 技術(shù)預(yù)研,PHP7升級(jí)準(zhǔn)備。

  • 環(huán)境編譯和搭建,下載相關(guān)的編譯包,搭建完整的編譯環(huán)境和測(cè)試環(huán)境。(編譯環(huán)境還是需要比較多的依賴so)

  • 兼容升級(jí)和測(cè)試。PHP7擴(kuò)展的重新編譯和代碼兼容性工作,AMS功能驗(yàn)證,性能壓測(cè)。

  • 線上灰度。打包為pkg的安裝包,編寫相關(guān)的安裝shell安裝執(zhí)行代碼(包括軟鏈接、解決一些so依賴)。然后,灰度安裝到現(xiàn)網(wǎng),觀察。

  • 正式發(fā)布。擴(kuò)大灰度范圍,全量升級(jí)。

    因?yàn)閺腜HP5.2升級(jí)到PHP5.6的過(guò)程中,很多問(wèn)題已經(jīng)被我們提前解決了,所以,PHP7的升級(jí)主要難點(diǎn)在于tphplib擴(kuò)展的編譯升級(jí)。
    涉及主要的工作包括:

  • PHP5.6的擴(kuò)展到PHP7.0的比較大幅度改造升級(jí)(工作量比較大的地方)

  • 兼容apcu的內(nèi)存操作函數(shù)的改名。PHP5的時(shí)候,我們使用的apc前綴的函數(shù)不可用了,同步變?yōu)閍pcu前綴的函數(shù)(需要apcu擴(kuò)展)。

  • 語(yǔ)法兼容升級(jí)。實(shí)際上工作量不算大,從PHP5.6升級(jí)到PHP7變化并不多。

我們大概在2016年4月中旬份完成了PHP7和Apache的編譯工作, 4月下旬進(jìn)行現(xiàn)網(wǎng)灰度,5月初全量發(fā)布到其中一個(gè)現(xiàn)網(wǎng)集群。

2. 升級(jí)過(guò)程中的錯(cuò)誤調(diào)試方法

在升級(jí)和重新編譯PHP7擴(kuò)展時(shí),如果執(zhí)行結(jié)果不符合預(yù)期或者進(jìn)程core掉,很多錯(cuò)誤都是無(wú)法從error日志里看見(jiàn)的,不利于分析問(wèn)題??梢圆捎靡韵聨追N方法,可以用來(lái)定位和分析大部分的問(wèn)題:

  • var_dump/exit
    從PHP代碼層逐步輸出信息和執(zhí)行exit,可以逐步定位到異常執(zhí)行的PHP函數(shù)位置,然后再根據(jù)PHP函數(shù)名,反查擴(kuò)展內(nèi)的實(shí)現(xiàn)函數(shù),找到問(wèn)題。這種方法比較簡(jiǎn)單,但是效率不高。

  • gdb –p/gdb c
    這種方法主要用于分析進(jìn)程core的場(chǎng)景,我們采用的編譯方式,是將mod_php(PHP變成Apache的子或塊的方式),使用gdb –p來(lái)監(jiān)控Apache的服務(wù)進(jìn)程。
    命令:ps aux|grep httpd

    gdb調(diào)試指定進(jìn)程:
    命令:gdb -p

    使用c進(jìn)行捕獲,然后構(gòu)造能夠?qū)е耤ore的web請(qǐng)求:

    Apache通常是多進(jìn)程模式,為了讓問(wèn)題比較容易復(fù)現(xiàn),可以在http.con里修改參數(shù),將啟動(dòng)進(jìn)程數(shù)修改為1個(gè)(下圖中的多個(gè)參數(shù)都需要調(diào)整,以達(dá)到只啟動(dòng)單進(jìn)程單線程的目的)。

    當(dāng)然還有一種更簡(jiǎn)單的方法,因?yàn)锳pache本身就支持單進(jìn)程調(diào)試模式的。
    ./apachectl -k start -X -e debug
    然后再通過(guò)gdb –p來(lái)調(diào)試就更簡(jiǎn)單一些。

  • 通過(guò)strace命令查看Apache進(jìn)程具體在做了些什么事情,根據(jù)里面的執(zhí)行內(nèi)容,分析和定位問(wèn)題。
    strace -Ttt -v -s1024 -f -p pid(進(jìn)程id)
    備注:執(zhí)行這些命令,注意權(quán)限問(wèn)題,很可能需要root權(quán)限。

四.PHP5.6到PHP7.0擴(kuò)展升級(jí)實(shí)踐記錄

1. 數(shù)據(jù)類型的變化
  • zval
    php7的誕生始于zval結(jié)構(gòu)的變化,PHP7不再需要指針的指針,絕大部分zval**需要修改成zval*。如果PHP7直接操作zval,那么zval*也需要改成zval,Z_*P()也要改成Z_*(),ZVAL_*(var, …)需要改成ZVAL_*(&var, …),一定要謹(jǐn)慎使用&符號(hào),因?yàn)镻HP7幾乎不要求使用zval*,那么很多地方的&也是要去掉的。
    ALLOC_ZVAL,ALLOC_INIT_ZVAL,MAKE_STD_ZVAL這幾個(gè)分配內(nèi)存的宏已經(jīng)被移除了。大多數(shù)情況下,zval*應(yīng)該修改為zval,而INIT_PZVAL宏也被移除了。

/* 7.0zval結(jié)構(gòu)源碼 */
/* value字段,僅占一個(gè)size_t長(zhǎng)度,只有指針或double或者long */
typedef union _zend_value {
    zend_long         lval;                /* long value */
    double            dval;                /* double value */
    zend_refcounted  *counted;
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

struct _zval_struct {
    zend_value        value;            /* value */
    union {
        。。。
    } u1;/* 擴(kuò)充字段,主要是類型信息 */
    union {
        … …
    } u2;/* 擴(kuò)充字段,保存輔助信息 */
};
  • 整型
    直接切換即可:
    long->zend_long

    /* 定義 */
    typedef int64_t zend_long;
    /* else */
    typedef int32_t zend_long;


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

    struct _zend_string {
    zend_refcounted_h gc;
    zend_ulong        h;                /* hash value */
    size_t            len;
    char              val[1];
    };


    zend_stringchar*的轉(zhuǎn)換:

    zend_string *str;
    char *cstr = NULL;
    size_t slen = 0;
    //...
    /* 從zend_string獲取char* 和 len的方法如下 */
    cstr = ZSTR_VAL(str);
    slen = ZSTR_LEN(str);
    /* char* 構(gòu)造zend_string的方法 */
    zend_string * zstr = zend_string_init("test",sizeof("test"), 0);


    擴(kuò)展方法,解析參數(shù)時(shí),使用字符串的地方,將‘s’替換成‘S’:

    /* 例如 */
    `zend_string` `*zstr`;
    if (zend_parse_parameters(ZEND_NUM_ARGS() , "S", &zstr) == FAILURE)
    {
    RETURN_LONG(-1);
    }


  • 自定義對(duì)象
    源代碼:

    /* php7.0 zend_object 定義 */
    struct _zend_object {
    zend_refcounted_h gc;
    uint32_t          handle;
    zend_class_entry  *ce;
    const zend_object_handlers  *handlers;
    HashTable        *properties;
    zval              properties_table[1];
    };

    zendobject是一個(gè)可變長(zhǎng)度的結(jié)構(gòu)。因此在自定義對(duì)象的結(jié)構(gòu)中,zendobject需要放在最后一項(xiàng):

    /* 例子 */
    struct clogger_object {
    CLogger *logger;
    zend_object  std;// 放在后面
    };
    /* 使用偏移量的方式獲取對(duì)象 */
    static inline clogger_object *php_clogger_object_from_obj(zend_object *obj) {
    return (clogger_object*)((char*)(obj) - XtOffsetOf(clogger_object, std));
    }
    #define Z_USEROBJ_P(zv) php_clogger_object_from_obj(Z_OBJ_P((zv)))
    /* 釋放資源時(shí) */
    void tphp_clogger_free_storage(zend_object *object TSRMLS_DC)
    {
    clogger_object *intern = php_clogger_object_from_obj(object);
    if (intern->logger)
    {
        delete intern->logger;
        intern->logger = NULL;
    }
    zend_object_std_dtor(&intern->std);
    }


  • 數(shù)組
    7.0中的hash表定義如下,給出了一些注釋:

/*7.0中的hash表結(jié)構(gòu) */
typedef struct _Bucket { /* hash表中的一個(gè)條目 */
zval              val;   /* 刪除元素zval類型標(biāo)記為IS_UNDEF */
zend_ulong        h;                /* hash value (or numeric index)   */
zend_string      *key;              /* string key or NULL for numerics */
} Bucket;        
typedef struct _zend_array HashTable;    
struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;
    Bucket           *arData; /* 保存所有數(shù)組元素 */
    uint32_t          nNumUsed; /* 當(dāng)前用到了多少長(zhǎng)度, */
    uint32_t          nNumOfElements; /* 數(shù)組中實(shí)際保存的元素的個(gè)數(shù),一旦nNumUsed的值到達(dá)nTableSize,PHP就會(huì)嘗試調(diào)整arData數(shù)組,讓它更緊湊,具體方式就是拋棄類型為UDENF的條目 */
    uint32_t          nTableSize; /* 數(shù)組被分配的內(nèi)存大小為2的冪次方(最小值為8) */
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};

其中,PHP7在zend_hash.h中定義了一系列宏,用來(lái)操作數(shù)組,包括遍歷key、遍歷value、遍歷key-value等,下面是一個(gè)簡(jiǎn)單例子:

/* 數(shù)組舉例 */
zval *arr;
zend_parse_parameters(ZEND_NUM_ARGS() , "a", &arr_qos_req);
if (arr)
{
    zval *item;
    zend_string *key;
    ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(arr), key, item) {
        /* ... */
    }
}
/* 獲取到item后,可以通過(guò)下面的api獲取long、double、string值 */
zval_get_long(item) 
zval_get_double(item) 
zval_get_string(item)

PHP5.6版本中是通過(guò)zend_hash_find查找key,然后將結(jié)果給到zval **變量,并且查詢不到時(shí)需要自己分配內(nèi)存,初始化一個(gè)item,設(shè)置默認(rèn)值。

2. PHP7中的api變化
  • duplicate參數(shù)
    PHP5.6中很多API中都需要填入一個(gè)duplicate參數(shù),表明一個(gè)變量是否需要復(fù)制一份,尤其是string類的操作,PHP7.0中取消duplicate參數(shù),對(duì)于string相關(guān)操作,只要有duplicate參數(shù),直接刪掉即可。因?yàn)镻HP7.0中定義了zval_string結(jié)構(gòu),對(duì)字符串的操作,不再需要duplicate值,底層直接使用zend_string_init初始化一個(gè)zend_string即可,而在PHP5.6中string是存放在zval中的,而zval的內(nèi)存需要手動(dòng)分配。
    涉及的API匯總?cè)缦拢?br/> 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_STRINGLRETVAL_STRING、RETVAL_STRINGL、RETURN_STRING、RETURN_STRINGL

  • MAKE_STD_ZVAL
    PHP5.6中,zval變量是在堆上分配的,創(chuàng)建一個(gè)zval變量需要先聲明一個(gè)指針,然后使用MAKE_STD_ZVAL進(jìn)行分配空間。PHP7.0中,這個(gè)宏已經(jīng)取消,變量在棧上分配,直接定義一個(gè)變量即可,不再需要MAKE_STD_ZVAL,使用到的地方,直接去掉就好。

  • ZEND_RSRC_DTOR_FUNC

修改參數(shù)名rsrc為res
/* PHP5.6 */
typedef struct _zend_rsrc_list_entry {
    void *ptr;
    int type;
    int refcount;
} zend_rsrc_list_entry;
typedef void (*rsrc_dtor_func_t)(zend_rsrc_list_entry *rsrc TSRMLS_DC);
#define ZEND_RSRC_DTOR_FUNC(name)        void name(zend_rsrc_list_entry *rsrc TSRMLS_DC)

/* PHP7.0 */
struct _zend_resource {
    zend_refcounted_h gc;/*7.0中對(duì)引用計(jì)數(shù)做了結(jié)構(gòu)封裝*/
    int               handle;
    int               type;
    void             *ptr;
};
typedef void (*rsrc_dtor_func_t)(zend_resource *res);
#define ZEND_RSRC_DTOR_FUNC(name) void name(zend_resource *res)

PHP7.0中,將zend_rsrc_list_entry結(jié)構(gòu)升級(jí)為zend_resource,在新版本中只需要修改一下參數(shù)名稱即可。

  • 二級(jí)指針宏,即Z_*_PP
    PHP7.0中取消了所有的PP宏,大部分情況直接使用對(duì)應(yīng)的P宏即可。

  • zend_object_store_get_object被取消
    根據(jù)官方wiki,可以定義如下宏,用來(lái)獲取object,實(shí)際情況看,這個(gè)宏用的還是比較頻繁的:

    static inline user_object *user_fetch_object(zend_object *obj) {
    return (user_object *)((char*)(obj) - XtOffsetOf(user_object, std));
    }
    /* }}} */ 
    #define Z_USEROBJ_P(zv) user_fetch_object(Z_OBJ_P((zv)))


  • zend_hash_exists、zend_hash_find
    對(duì)所有需要字符串參數(shù)的函數(shù),PHP5.6中的方式是傳遞兩個(gè)參數(shù)(char* + len),而PHP7.0中定義了zend_string,因此只需要一個(gè)zend_string變量即可。
    返回值變成了zend_bool類型:

    /* 例子 */
    zend_string * key;  
    key = zend_string_init("key",sizeof("key"), 0);
    zend_bool res_key = zend_hash_exists(itmeArr, key);


  • 參考資料:
    1、php5 to phpng,http://yaoguais.com/?s=md/php/php7-vm.md
    2、PHP擴(kuò)展開發(fā)及內(nèi)核應(yīng)用, http://www.walu.cc/phpbook/10.1.md
    3、PHP 7中新的Hashtable實(shí)現(xiàn)和性能改進(jìn) ,http://gywbd.github.io/posts/2014/12/php7-new-hashtable-implementation.html
    4、深入理解PHP7之zval, https://github.com/laruence/php7-internal/blob/master/zval.md
    5、官方wiki, https://wiki.php.net/phpng-upgrading
    6、php手冊(cè) ,http://php.net/manual/zh/index.php
    7、PHP7 使用資源包裹第三方擴(kuò)展的實(shí)現(xiàn)及其源碼解讀 ,https://mengkang.net/684.html

五.AMS平臺(tái)升級(jí)PHP7的性能優(yōu)化成果

現(xiàn)網(wǎng)服務(wù)是一個(gè)非常重要而又敏感的環(huán)境,輕則影響用戶體驗(yàn),重則產(chǎn)生現(xiàn)網(wǎng)事故。因此,我們4月下旬完成PHP7編譯和測(cè)試工作之后,就在AMS其中一臺(tái)機(jī)器進(jìn)行了灰度上線,觀察了幾天后,然后逐步擴(kuò)大灰度范圍,在5月初完成升級(jí)。
這個(gè)是我們壓測(cè)AMS一個(gè)查詢多個(gè)活動(dòng)計(jì)數(shù)器的壓測(cè)結(jié)果,以及現(xiàn)網(wǎng)CGI機(jī)器,在高峰相同TGW流量場(chǎng)景下的CPU負(fù)載數(shù)據(jù):

就我們的業(yè)務(wù)壓測(cè)和現(xiàn)網(wǎng)結(jié)果來(lái)看,和官方所說(shuō)的性能提升一倍,基本一致。


AMS平臺(tái)擁有不少的CGI機(jī)器,PHP7的升級(jí)和應(yīng)用給我們帶來(lái)了性能的提升,可以有效節(jié)省硬件資源成本。并且,通過(guò)Apache2.4的Event模式,我們也增強(qiáng)了Apache在支持并發(fā)方面的能力。

我們PHP7升級(jí)研發(fā)項(xiàng)目組,在過(guò)去比較長(zhǎng)的一個(gè)時(shí)間段里,經(jīng)過(guò)持續(xù)地努力和推進(jìn),終于在2016年4月下旬現(xiàn)網(wǎng)灰度,5月初在集群中全量升級(jí),為我們的AMS活動(dòng)運(yùn)營(yíng)平臺(tái)帶來(lái)性能上大幅度的提升。
PHP7的革新,對(duì)于PHP語(yǔ)言本身而言,具有非凡的意義和價(jià)值,這讓我更加確信一點(diǎn),PHP會(huì)是一個(gè)越來(lái)越好的語(yǔ)言。同時(shí),感謝PHP社區(qū)的開發(fā)者們,為我們業(yè)務(wù)帶來(lái)的性能提升。

上述就是小編為大家分享的如何進(jìn)行QQ會(huì)員AMS平臺(tái)PHP7升級(jí)了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI