日請求億級的QQ會員AMS平臺PHP7升級實踐
QQ會員活動運營平臺(AMS),是QQ會員增值運營業務的重要載體之一,承擔海量活動運營的Web系統。AMS是一個主要採用PHP語言實現的活動運營平臺, CGI日請求3億左右,高峰期達到8億。然而,在之前比較長的一段時間裡,我們都採用了比較老舊的基礎軟體版本,就是PHP5.2+Apache2.0(2008年的技術)。尤其從去年開始,隨著AMS業務隨著QQ會員增值業務的快速增長,效能壓力日益變大。
於是,自2015年5月,我們就開始規劃PHP底層升級,最終的目標是升級到PHP7。那時,PHP7尚處於研發階段,而我們討論和預研就已經開始了。
一、PHP7的學習和預研 1. HHVM和JIT
2015年就PHP效能優化的方案,有另外一個比較重要的角色,就是由Facebook開源的HHVM(HipHop Virtual Machine,HHVM是一個Facebook開源的PHP虛擬機器)。HHVM使用JIT(Just In Time,即時編譯是種軟體優化技術,指在執行時才會去編譯位元組碼為機器碼)的編譯方式以及其他技術,讓PHP程式碼的執行效能大幅提升。據傳,可以將PHP5版本的原生PHP程式碼提升5-10倍的執行效能。
HHVM起源於Facebook公司,Facebook早起的很多程式碼是使用PHP來開發的,但是,隨著業務的快速發展,PHP執行效率成為越來越明顯的問題。為了優化執行效率,Facebook在2008年就開始使用HipHop,這是一種PHP執行引擎,最初是為了將 Fackbook的大量PHP程式碼轉成 C++,以提高效能和節約資源。使用HipHop的PHP程式碼在效能上有數倍的提升。後來,Facebook將HipHop平臺開源,逐漸發展為現在的 HHVM。
HHVM成為一個PHP效能優化解決方案時,PHP7還處於研發階段。曾經看過部分同學對於HHVM的交流,效能可以獲得可觀的提升,但是服務運維和PHP語法相容有一定成本。有一陣子,JIT成為一個呼聲很高的東西,很多技術同學建議PHP7也應該通過JIT來優化效能。
2015年7月,我參加了中國PHPCON,聽了惠新宸關於PHP7核心的技術分享。實際上,在2013年的時候,惠新宸(PHP7核心開發者)和Dmitry(另一位PHP語言核心開發者之一)就曾經在PHP5.5的版本上做過一個JIT的嘗試(並沒有釋出)。PHP5.5的原來的執行流程,是將PHP程式碼通過詞法和語法分析,編譯成opcode位元組碼(格式和彙編有點像),然後,Zend引擎讀取這些opcode指令,逐條解析執行。
而他們在opcode環節後引入了型別推斷(TypeInf),然後通過JIT生成ByteCodes,然後再執行。
於是,在benchmark(測試程式)中得到非常好的結果,實現JIT後效能比PHP5.5提升了8倍。然而,當他們把這個優化放入到實際的專案WordPress(一個開源部落格專案)中,卻幾乎看不見效能的提升。原因在於測試專案的程式碼量比較少,通過JIT產生的機器碼也不大,而真實的WordPress專案生成的機器碼太大,引起CPU快取命中率下降(CPU Cache Miss)。
總而言之,JIT並非在每個場景下都是點石成金的利器,而脫離業務場景的效能測試結果,並不一定具有代表性。
從官方放出Wordpress的PHP7和HHVM的效能對比可以看出,兩者基本處於同一水平。
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),讓編譯器提前完成部分工作。無需在程式執行時分配記憶體,能夠實現類似函式的功能,卻沒有函式呼叫的壓棧、彈棧開銷,效率會比較高。
… …
更多更詳細關於PHP7的介紹,有興趣的同學可以檢視:《 PHP7革新與效能優化 》
3.AMS平臺技術選型的背景
就提升PHP的效能而言,可以選擇的是2015年就可直接使用的HHVM或者是2015年底才釋出正式版的PHP7。會員AMS是一個訪問量級比較大的一個Web系統,經過四年持續的升級和優化,積累了800多個業務功能元件,還有各種PHP編寫的公共基礎庫和指令碼,程式碼規模也比較大。
我們對於PHP版本對程式碼的向下相容的需求是比較高的,因此,就我們業務場景而言,PHP7良好的語法向下相容,正是我們所需要的。因此,我們選擇以PHP7為升級的方案。
二、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,在技術熟悉和經驗方面積累更多,因此,它可能不是最佳的選擇,但是,具體到我們業務場景,算是比較合適的一個選擇。
三、版本升級實施過程 1.高跨度版本升級方式
從一個2008年的Apache2.0直接升級到2016年的Apache2.4,這個跨度過於大,甚至使用的http.conf的配置檔案都有很多的不同,這裡的需要更新的地方比較多,未知的風險也是存在的。於是,我們的做法,是先嚐試將Apache2.0升級到Apach2.2,調整配置、觀察穩定性,然後再進一步嘗試到Apach2.4。所幸的是,Apache(httpd)是一個比較特別的開源社群,他們之前一直同時維護這兩個分支版本的Apache(2.2和2.4),因此,即使是Apache2.2也有比較新的版本。
於是,我們先升級了一個PHP5.2+Apache2.2,對相容性進行了測試和觀察,確認兩者之間是可以比較平滑升級後,我們開始進行Apache2.4的升級方案。
PHP5.2的升級,我們也採用相同的思路,我們先將PHP5.2升級至PHP5.6(當時,PHP7還是beta版本),然後再將PHP5.6升級到PHP7,以更平滑的方式,逐步解決不同的問題。
於是,我們的升級計劃變為:
Apache2.4編譯為動態MPM的模式(支援通過httpd配置切換prefork/worker/event模式),根據現網風險等實時降級。
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
從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)正式釋出。擴大灰度範圍,全量升級。
因為從PHP5.2升級到PHP5.6的過程中,很多問題已經被我們提前解決了,所以,PHP7的升級主要難點在於tphplib擴充套件的編譯升級。
涉及主要的工作包括:
(1)PHP5.6的擴充套件到PHP7.0的比較大幅度改造升級(工作量比較大的地方)
(2)相容apcu的記憶體操作函式的改名。PHP5的時候,我們使用的apc字首的函式不可用了,同步變為apcu字首的函式(需要apcu擴充套件)。
(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
gdb除錯指定程式:
命令:gdb -p
使用c進行捕獲,然後構造能夠導致core的web請求:
Apache通常是多程式模式,為了讓問題比較容易復現,可以在http.con裡修改引數,將啟動程式數修改為1個(下圖中的多個引數都需要調整,以達到只啟動單程式單執行緒的目的)。
當然還有一種更簡單的方法,因為Apache本身就支援單程式除錯模式的。
./apachectl -k start -X -e debug
然後再通過gdb –p來除錯就更簡單一些。
(3)通過strace命令檢視Apache程式具體在做了些什麼事情,根據裡面的執行內容,分析和定位問題。
strace -Ttt -v -s1024 -f -p pid(程式id)
備註:執行這些命令,注意許可權問題,很可能需要root許可權。
四、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巨集也被移除了。
/* 7.0zval結構原始碼 */ /* value欄位,僅佔一個size_t長度,只有指標或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;/* 擴充欄位,主要是型別資訊 */ union { … … } u2;/* 擴充欄位,儲存輔助資訊 */ };
(2)整型
直接切換即可:
long->zend_long
/* 定義 */ typedef int64_t zend_long; /* else */ typedef int32_t zend_long;
(3)字串型別
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_string和char*的轉換:
zend_string *str; char *cstr = NULL; size_t slen = 0; //... /* 從zend_string獲取char* 和 len的方法如下 */ cstr = ZSTR_VAL(str); slen = ZSTR_LEN(str); /* char* 構造zend_string的方法 */ zend_string * zstr = zend_string_init("test",sizeof("test"), 0);
擴充套件方法,解析引數時,使用字串的地方,將‘s’替換成‘S’:
/* 例如 */ zend_string *zstr; if (zend_parse_parameters(ZEND_NUM_ARGS() , "S", &zstr) == FAILURE) { RETURN_LONG(-1); }
(4)自定義物件
原始碼:
/* 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]; };
zend_object是一個可變長度的結構。因此在自定義物件的結構中,zend_object需要放在最後一項:
/* 例子 */ struct clogger_object { CLogger *logger; zend_object std;// 放在後面 }; /* 使用偏移量的方式獲取物件 */ 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))) /* 釋放資源時 */ 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); }
(5)陣列
7.0中的hash表定義如下,給出了一些註釋: /* 7.0中的hash表結構 */ typedef struct _Bucket { /* hash表中的一個條目 */ zval val; /* 刪除元素zval型別標記為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; /* 儲存所有陣列元素 */ uint32_t nNumUsed; /* 當前用到了多少長度, */ uint32_t nNumOfElements; /* 陣列中實際儲存的元素的個數,一旦nNumUsed的值到達nTableSize,PHP就會嘗試調整arData陣列,讓它更緊湊,具體方式就是拋棄型別為UDENF的條目 */ uint32_t nTableSize; /* 陣列被分配的記憶體大小為2的冪次方(最小值為8) */ uint32_t nInternalPointer; zend_long nNextFreeElement; dtor_func_t pDestructor; };
其中,PHP7在zend_hash.h中定義了一系列巨集,用來運算元組,包括遍歷key、遍歷value、遍歷key-value等,下面是一個簡單例子:
/* 陣列舉例 */ 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後,可以通過下面的api獲取long、double、string值 */ zval_get_long(item) zval_get_double(item) zval_get_string(item)
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
/* 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中對引用計數做了結構封裝*/ 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結構升級為zend_resource,在新版本中只需要修改一下引數名稱即可。
(4)二級指標巨集,即Z_*_PP
PHP7.0中取消了所有的PP巨集,大部分情況直接使用對應的P巨集即可。
(5)zend_object_store_get_object被取消
根據官方wiki,可以定義如下巨集,用來獲取object,實際情況看,這個巨集用的還是比較頻繁的:
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)))
(6)zend_hash_exists、zend_hash_find
對所有需要字串引數的函式,PHP5.6中的方式是傳遞兩個引數(char* + len),而PHP7.0中定義了zend_string,因此只需要一個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擴充套件開發及核心應用:http://www.walu.cc/phpbook/10.1.md
3. PHP 7中新的Hashtable實現和效能改進: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手冊:http://php.net/manual/zh/index.php
7. PHP7 使用資源包裹第三方擴充套件的實現及其原始碼解讀:https://mengkang.net/684.html
五、AMS平臺升級PHP7的效能優化成果
現網服務是一個非常重要而又敏感的環境,輕則影響使用者體驗,重則產生現網事故。因此,我們4月下旬完成PHP7編譯和測試工作之後,就在AMS其中一臺機器進行了灰度上線,觀察了幾天後,然後逐步擴大灰度範圍,在5月初完成升級。
這個是我們壓測AMS一個查詢多個活動計數器的壓測結果,以及現網CGI機器,在高峰相同TGW流量場景下的CPU負載資料:
就我們的業務壓測和現網結果來看,和官方所說的效能提升一倍,基本一致。
AMS平臺擁有不少的CGI機器,PHP7的升級和應用給我們帶來了效能的提升,可以有效節省硬體資源成本。並且,通過Apache2.4的Event模式,我們也增強了Apache在支援併發方面的能力。
六、小結
我們PHP7升級研發專案組,在過去比較長的一個時間段裡,經過持續地努力和推進,終於在2016年4月下旬現網灰度,5月初在叢集中全量升級,為我們的AMS活動運營平臺帶來效能上大幅度的提升。
PHP7的革新,對於PHP語言本身而言,具有非凡的意義和價值,這讓我更加確信一點,PHP會是一個越來越好的語言。同時,感謝PHP社群的開發者們,為我們業務帶來的效能提升。
相關文章
- QQ會員活動運營平臺演變實踐
- 日均請求量百億級資料處理平臺的容器雲實踐
- MySQL 升級的最佳實踐MySql
- 青團社:億級靈活用工平臺的雲原生架構實踐架構
- Scrapy的日誌等級和請求傳參
- 阿里雲 PB 級 Kubernetes 日誌平臺建設實踐阿里
- 全國車險資訊平臺升級
- 億級流量實驗平臺設計與實現
- 升級Webpack5實踐Web
- 升級 PHP7 過程記錄PHP
- AWS RDS強制升級的應對之道——版本升級的最佳實踐
- 當詐騙平臺Steam管家升級為“全家桶”平臺
- 最佳實踐 | 原始碼升級gcc原始碼GC
- DRF對Django請求響應做了技術升級Django
- datapump跨平臺升級遷移的總結
- BI 資料視覺化平臺建設(2)—篩選器元件升級實踐視覺化元件
- HDFS3.2升級在滴滴的實踐S3
- 自定義Egg.js的請求級別日誌JS
- Centos 7升級 PHP7 到 PHP8CentOSPHP
- [公告]看雪會員等級體系、權益及會員升級策略介紹(2018版)
- scrapy處理post請求的傳參和日誌等級
- 愛奇藝星鑽VIP會員怎麼升級? 愛奇藝成為星鑽VIP會員的技巧
- flutter跨平臺開發之App升級方案FlutterAPP
- 浪潮助力手機大資料平臺升級大資料
- 微信後臺非同步訊息佇列的優化升級實踐分享非同步佇列優化
- 百分點萬億級大資料平臺的建設實踐大資料
- AIX平臺升級11.2需要注意的補丁AI
- 大規模 Hadoop 升級在 Pinterest 的實踐HadoopREST
- 作為擁有6億日活用量的“國民級”短視訊平臺—抖音
- vivo 萬臺規模 HDFS 叢集升級 HDFS 3.x 實踐
- MySQL升級會變慢?MySql
- 億級使用者下的新浪微博平臺架構架構
- 會員半價日消費升級 易到堅持打造差異化商業模式模式
- Homebrew學習及mac下升級php7問題MacPHP
- 傳真系統的跨平臺相容和更換升級
- 億級使用者中心的設計與實踐
- Kubernetes 叢集無損升級實踐
- dubbo2升級到dubbo3實踐