京東活動(dòng)系統(tǒng)億級(jí)流量應(yīng)對(duì)之術(shù)
背景
京東活動(dòng)系統(tǒng)是一個(gè)可在線編輯、實(shí)時(shí)編輯更新和發(fā)布新活動(dòng),并對(duì)外提供頁(yè)面訪問(wèn)服務(wù)的系統(tǒng),地址如http://sale.jd.com/***.html。其高時(shí)效性、靈活性等特征,極受青睞,已發(fā)展成京東幾個(gè)重要流量入口之一。近幾次大促,系統(tǒng)所承載的PV均為數(shù)億以上。隨著京東業(yè)務(wù)的高速發(fā)展,京東活動(dòng)系統(tǒng)的壓力會(huì)越來(lái)越大。急需要一個(gè)更高效,穩(wěn)定的系統(tǒng)架構(gòu),來(lái)支持業(yè)務(wù)的高速發(fā)展。本文主要對(duì)活動(dòng)頁(yè)面瀏覽方面的性能,進(jìn)行探討。
活動(dòng)頁(yè)面瀏覽性能提升的難點(diǎn):
- 活動(dòng)與活動(dòng)之間差異很大,不像商品頁(yè)有固定的模式。每個(gè)頁(yè)面能抽取的公共部分有限,可復(fù)用性差;
- 活動(dòng)頁(yè)面內(nèi)容多樣,業(yè)務(wù)繁多。依賴大量外部業(yè)務(wù)接口,數(shù)據(jù)很難做到閉環(huán)。外部接口的性能,以及穩(wěn)定性,嚴(yán)重制約了活動(dòng)頁(yè)的渲染速度、穩(wěn)定性;
經(jīng)過(guò)多年在該系統(tǒng)下的開(kāi)發(fā)實(shí)踐,提出“頁(yè)面渲染與頁(yè)面瀏覽異步化”的思想, 頁(yè)面渲染是把渲染好的整頁(yè)數(shù)據(jù)放到redis 或者硬盤(pán)里了,頁(yè)面瀏覽是從redis或者硬盤(pán)里取靜態(tài)的頁(yè)面,并以此為指導(dǎo),對(duì)該系統(tǒng)進(jìn)行架構(gòu)升級(jí)改造。通過(guò)近幾個(gè)月的運(yùn)行,各方面性能都有顯著提升。在分享"新架構(gòu)"之前,先看看我們現(xiàn)有web系統(tǒng)的架構(gòu)現(xiàn)狀。
web架構(gòu)發(fā)展與現(xiàn)狀
* 瀏覽服務(wù)
以京東活動(dòng)系統(tǒng)架構(gòu)的演變?yōu)槔@里沒(méi)有畫(huà)出具體的業(yè)務(wù)邏輯,只是簡(jiǎn)單的描述下架構(gòu)。
我們會(huì)在消耗性能的地方加緩存,這里對(duì)部分查庫(kù)操作加redis緩存。
并且對(duì)頁(yè)面進(jìn)行整頁(yè)redis緩存:由于活動(dòng)頁(yè)面內(nèi)容繁多,渲染一次頁(yè)面的成本是很高。這里可以考慮把渲染好的活動(dòng)內(nèi)容整頁(yè)緩存起來(lái),下次請(qǐng)求到來(lái)時(shí),如果緩存中有值,直接獲取緩存返回。
以上是系統(tǒng)應(yīng)用服務(wù)層面架構(gòu)演進(jìn)的,簡(jiǎn)單示意。為了減少應(yīng)用服務(wù)器的壓力,可以在應(yīng)用服務(wù)器前面,加cdn和nginx的proxy_cache,減少回源率。
整體架構(gòu)(老)
除了“瀏覽服務(wù)”外,老架構(gòu)還做了其他兩個(gè)大的優(yōu)化:“接口服務(wù)”、“靜態(tài)服務(wù)”
1.訪問(wèn)請(qǐng)求,首先到達(dá)瀏覽服務(wù),把整個(gè)頁(yè)面框架返回給瀏覽器(有cdn、nginx、redis等各級(jí)緩存);
2.對(duì)于實(shí)時(shí)數(shù)據(jù)(如秒殺)、個(gè)性化數(shù)據(jù)(如登陸、個(gè)人坐標(biāo)),采用前端實(shí)時(shí)接口調(diào)用,前端接口服務(wù);
3.靜態(tài)服務(wù):靜態(tài)資源分離,所有靜態(tài)js、css訪問(wèn)靜態(tài)服務(wù);
4.要點(diǎn):瀏覽服務(wù)、接口服務(wù)分離。頁(yè)面固定不變部分走瀏覽服務(wù),實(shí)時(shí)變化、個(gè)性化采用前端接口服務(wù)實(shí)現(xiàn)。
接口服務(wù)分兩類,直接讀redis緩存和調(diào)用外部接口。這里可以對(duì)直接讀redis的接口采用nginx+lua(openresty)進(jìn)行優(yōu)化,不做詳細(xì)講解。 本次分享主要對(duì)“瀏覽服務(wù)”架構(gòu)。
新老架構(gòu)性能對(duì)比
在講新架構(gòu)之前先看看新老架構(gòu)下的新能對(duì)比。
* 老架構(gòu)瀏覽服務(wù)性能
擊穿cdn緩存、nginx緩存,回源到應(yīng)用服務(wù)器的流量大約為20%-40%之間,這里的性能對(duì)比,只針對(duì)回源到應(yīng)用服務(wù)器的部分。
瀏覽方法TP99如下(物理機(jī))
TP99 1000ms左右,且抖動(dòng)幅度很大,內(nèi)存使用近70%,cpu 45%左右。1000ms內(nèi)沒(méi)有緩存,有阻塞甚至掛掉的風(fēng)險(xiǎn)。
* 新架構(gòu)瀏覽服務(wù)性能
本次2016 618采用新架構(gòu)支持,瀏覽TP99如下(分app端活動(dòng)和pc端活動(dòng))
移動(dòng)活動(dòng)瀏覽TP99穩(wěn)定在8ms, PC活動(dòng)瀏覽TP99 穩(wěn)定在15ms左右。全天幾乎一條直線,沒(méi)有性能抖動(dòng)。
新架構(gòu)支持,服務(wù)器(docker)cpu性能如下
cpu消耗一直平穩(wěn)在1%,幾乎沒(méi)有抖動(dòng)。
對(duì)比結(jié)果:新架構(gòu)TP99從1000ms降低到15ms,cpu消耗從45%降低到1%,新架構(gòu)性能得到質(zhì)的提升。
why!!! 下面我們就來(lái)揭開(kāi)新架構(gòu)的面紗。
新架構(gòu)探索
* 頁(yè)面渲染與頁(yè)面瀏覽異步化
再來(lái)看之前的瀏覽服務(wù)架構(gòu),20%-40%的頁(yè)面請(qǐng)求會(huì)重新渲染頁(yè)面,渲染需要重新計(jì)算、查詢、創(chuàng)建對(duì)象等導(dǎo)致 cpu、內(nèi)存消耗增加,TP99性能下降。
如果能保證每次請(qǐng)求都能獲取到redis整頁(yè)緩存,這些性能問(wèn)題就都不存在了。即:頁(yè)面渲染與頁(yè)面瀏覽異步。
* 直接改造后的問(wèn)題以及解決方案
理想情況下,如果頁(yè)面數(shù)據(jù)變動(dòng)可以通過(guò) 手動(dòng)觸發(fā)渲染(頁(yè)面發(fā)布新內(nèi)容)、外部數(shù)據(jù)變化通過(guò)監(jiān)聽(tīng)mq 自動(dòng)觸發(fā)渲染。
但是有些外部接口不支持mq、或者無(wú)法使用mq,比如活動(dòng)頁(yè)面置入的某個(gè)商品,這個(gè)商品名稱變化。
為了解決這個(gè)問(wèn)題,view工程每隔指定時(shí)間,向engine發(fā)起重新渲染請(qǐng)求-***內(nèi)容放入redis。下一次請(qǐng)求到來(lái)時(shí)即可獲取到新內(nèi)容。由于活動(dòng)很多,也不能確定哪些活動(dòng)在被訪問(wèn),所以不建議使用timer。通過(guò)加一個(gè)緩存key來(lái)實(shí)現(xiàn),處理邏輯如下。
好處就是,只對(duì)有訪問(wèn)的活動(dòng)定時(shí)重新發(fā)起渲染。
新架構(gòu)講解
* 整理架構(gòu)(不包含業(yè)務(wù))
view工程職責(zé):
- 直接從緩存或者硬盤(pán)中獲取靜態(tài)HTML返回,如果沒(méi)有返回錯(cuò)誤頁(yè)面(文件系統(tǒng)的存取性能比較低,超過(guò)100ms級(jí)別,這里沒(méi)有使用);
- 根據(jù)緩存key2是否過(guò)期,判斷是否向engine重新發(fā)起渲染(如果你的項(xiàng)目外面接口都支持mq,這個(gè)功能就不需要了)。
engine工程職責(zé):
- 渲染活動(dòng)頁(yè)面,把結(jié)果放到硬盤(pán)、redis。
publish工程、mq 職責(zé):
- 頁(yè)面發(fā)生變化,向engine重新發(fā)起渲染, 具體的頁(yè)面邏輯,這里不做講解。
Engine工程的工作就是當(dāng)頁(yè)面內(nèi)容發(fā)生變化時(shí),重新渲染頁(yè)面,并將整頁(yè)內(nèi)容放到redis,或者推送到硬盤(pán)。
* view工程架構(gòu)(redis版)
View工程的工作,就是根據(jù)鏈接從redis中獲取頁(yè)面內(nèi)容返回。
* view工程架構(gòu) (硬盤(pán)版)
兩個(gè)版本對(duì)比
Redis版
- 優(yōu)點(diǎn):接入簡(jiǎn)單、 性能好,尤其是在大量頁(yè)面情況下,沒(méi)有性能抖動(dòng) 。單個(gè)docker tps達(dá)到 700;
- 缺點(diǎn):嚴(yán)重依賴京東redis服務(wù),如果redis服務(wù)出現(xiàn)問(wèn)題,所有頁(yè)面都無(wú)法訪問(wèn)。
硬盤(pán)版
- 優(yōu)點(diǎn):不依賴任何其他外部服務(wù),只要應(yīng)用服務(wù)不掛、網(wǎng)絡(luò)正常就可以對(duì)外穩(wěn)定服務(wù);在頁(yè)面數(shù)量不大的情況下,性能優(yōu)越。單個(gè)docker tps達(dá)到 2000;
- 缺點(diǎn):在頁(yè)面數(shù)據(jù)量大的情況下(系統(tǒng)的所有活動(dòng)頁(yè)有xx個(gè)G左右),磁盤(pán)io消耗增加(這里采用的java io,如果采用nginx+lua(OpenResty),io消耗應(yīng)該會(huì)控制在10%以內(nèi))。
解決方案
- 對(duì)所有頁(yè)面訪問(wèn)和存儲(chǔ)采用url hash方式,所有頁(yè)面均勻分配到各個(gè)應(yīng)用服務(wù)器上;
- 采用nginx+lua(OpenResty)利用nginx的異步io,代替java io。
* Openresty+硬盤(pán)版
現(xiàn)在通過(guò)nginx+lua(OpenResty)做應(yīng)用服務(wù),所具有的高并發(fā)處理能力、高性能、高穩(wěn)定性已經(jīng)越來(lái)越受青睞。通過(guò)上述講解,view工程沒(méi)有任何業(yè)務(wù)邏輯??梢院茌p易的就可以用lua實(shí)現(xiàn),從redis或者硬盤(pán)獲取頁(yè)面,實(shí)現(xiàn)更高效的web服務(wù)。
通過(guò)測(cè)試對(duì)比,view工程讀本地硬盤(pán)的速度,比讀redis還要快(同一個(gè)頁(yè)面,讀redis是15ms,硬盤(pán)是8ms)。所以***版架構(gòu)我選擇用硬盤(pán),redis做備份,硬盤(pán)讀不到時(shí)在讀redis。
這里前置機(jī)的url hash是自己實(shí)現(xiàn)的邏輯,engine工程采用同樣的規(guī)則推送到view服務(wù)器硬盤(pán)即可,具體邏輯這里不細(xì)講。后面有時(shí)間再單獨(dú)做一次分享。
優(yōu)點(diǎn):
- 具備硬盤(pán)版的全部?jī)?yōu)點(diǎn),同時(shí)去掉tomcat,直接利用nginx高并發(fā)能力,以及io處理能力;
- 各項(xiàng)性能、以及穩(wěn)定性達(dá)到***。
缺點(diǎn):
- 硬盤(pán)壞掉,影響訪問(wèn);
- 方法監(jiān)控,以及日志打印,需使用lua腳本重寫(xiě)。
總結(jié)
無(wú)論是redis版、硬盤(pán)版、openresty+硬盤(pán)版,基礎(chǔ)都是頁(yè)面渲染與頁(yè)面瀏覽異步化。
優(yōu)勢(shì):
- 所有業(yè)務(wù)邏輯都剝離到engine工程,新view工程理論上永遠(yuǎn)無(wú)需上線;
- 災(zāi)備多樣化(redis、硬盤(pán)、文件系統(tǒng)),且更加簡(jiǎn)單,外部接口或者服務(wù)出現(xiàn)問(wèn)題后,切斷engine工程渲染,不再更新redis和硬盤(pán)即可;
- 新view工程,與業(yè)務(wù)邏輯完全隔離,不依賴外部接口和服務(wù),大促期間,即便外部接口出現(xiàn)新能問(wèn)題,或者有外部服務(wù)掛掉,絲毫不影響view工程正常訪問(wèn);
- 性能提升上百倍,從1000ms提升到10ms左右。詳見(jiàn)前面的性能截圖;
- 穩(wěn)定性:只要view服務(wù)器的網(wǎng)絡(luò)還正常,可以做到理論上用不掛機(jī);
- 大幅度節(jié)省服務(wù)器資源,按此架構(gòu),4+20+30=54個(gè)docker足以支持10億級(jí)PV。(4個(gè)nginx proxy_cache、20個(gè)view,30個(gè)engine)
作者: 干天星,2012年初加入京東,先后在京東審計(jì)、搭配購(gòu)、jshop活動(dòng)系統(tǒng)等項(xiàng)目從事系統(tǒng)研發(fā)和架構(gòu)工作。目前主要負(fù)責(zé)jshop活動(dòng)系統(tǒng)架構(gòu)升級(jí),以及jshop數(shù)據(jù)中心實(shí)現(xiàn)運(yùn)算架構(gòu)設(shè)計(jì)。對(duì)構(gòu)建高并發(fā)web架構(gòu),以及高性能實(shí)時(shí)大數(shù)據(jù)運(yùn)算,有一定的見(jiàn)解。入職前有過(guò)5年電信傳統(tǒng)行業(yè)開(kāi)發(fā)、架構(gòu)經(jīng)驗(yàn)。
【本文來(lái)自51CTO專欄作者張開(kāi)濤的微信公眾號(hào)(開(kāi)濤的博客),公眾號(hào)id: kaitao-1234567】