京东前端:三级列表页持续架构优化
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
王向維,京東商城三級列表頁架構(gòu)師。工作期間,完成了京東三級列表頁由Node.js版本到Nginx+Lua版本的變遷,并針對三級列表頁前端即服務(wù)器端做了大量的優(yōu)化工作。
1. 京東三級列表頁
三級列表頁是什么
列表頁是京東商城的三大核心系統(tǒng)之一。京東三級列表頁是用戶選取商品類型后,展示同類商品的頁面,具體如下圖所示。
(點擊放大圖像)
如何進入三級列表頁
用戶在首頁左側(cè)的導(dǎo)航樹中(如下圖所示)、全部商品分類列表頁或者頂部面包屑導(dǎo)航中,選擇到商品的最小分類級別后,就可以到達三級列表頁。
(點擊放大圖像)
相關(guān)廠商內(nèi)容
通過探針技術(shù),實現(xiàn)Java應(yīng)用程序自我防護
新Java,新未來
你離成為一位合格的技術(shù)領(lǐng)導(dǎo)者還有多遠?
你了解技術(shù)領(lǐng)導(dǎo)與技術(shù)管理的差別嗎?
QCon全球軟件開發(fā)大會上海站,10月20日-22日,上海·寶華萬豪酒店!
相關(guān)贊助商
QCon全球軟件開發(fā)大會上海站,2016年10月20日-22日,上海寶華萬豪酒店,精彩內(nèi)容搶先看!
三級列表頁的作用
該頁面根據(jù)用戶選擇的商品類目進行檢索,將結(jié)果以列表的形式展現(xiàn)在頁面上。使用戶快速地找到自己需要的產(chǎn)品,提高用戶購買轉(zhuǎn)化率。
三級列表頁的業(yè)務(wù)特點
涉及到多維度多因子質(zhì)量分綜合排序,排序質(zhì)量的效果直接關(guān)系到轉(zhuǎn)化率,客單價,進而影響到GMV,實質(zhì)上是數(shù)據(jù)挖掘和機器學(xué)習(xí)技術(shù)在海量數(shù)據(jù)上的應(yīng)用。
在不同三級類目下,通過復(fù)雜不確定的查詢條件、屬性區(qū)域和列表查詢結(jié)果的實時聯(lián)動,以及跟區(qū)域相關(guān)的庫存、京東配送、貨到付款等復(fù)雜業(yè)務(wù)邏輯下,做到高并發(fā)實時計算。
2. 優(yōu)化原因
三級列表頁的頁面周圍依賴的內(nèi)部系統(tǒng)太多,要做到異步化展示,阻塞可降級。
在持續(xù)開發(fā)一個核心系統(tǒng)過程中,除了滿足業(yè)務(wù)需求外,還應(yīng)該考慮系統(tǒng)未來的架構(gòu),追求極致的系統(tǒng)的可用性、高性能和穩(wěn)定性。這個過程是一個長期積累和重構(gòu)的過程,京東三級列表頁的優(yōu)化工作,就是這個過程的一部分。
優(yōu)化前的狀況
優(yōu)化前的三級列表頁有以下特點:
- 基于搜索實現(xiàn);
- 全量數(shù)據(jù),搜索結(jié)果不理想;
- 接口響應(yīng)時間長,影響了用戶體驗;
- 沒法針對數(shù)據(jù)做二次優(yōu)化;
- 轉(zhuǎn)化率相對較低;
基于以上原因,需要對三級列表頁做出改變,也就是對老版本進行重構(gòu)。
重構(gòu)版本目的
通過優(yōu)化,希望達到以下目的:
- 非全量數(shù)據(jù),線下異步根據(jù)數(shù)據(jù)模型進行進行篩選部分最優(yōu)數(shù)據(jù);
- 要求實時過濾計算,接口響應(yīng)時間要快,保證用戶體驗;
- 數(shù)據(jù)進行優(yōu)化,提高轉(zhuǎn)換率,提搞GMV;
- 實現(xiàn)前端降級和異步模塊出錯上報
3. 優(yōu)化原則
每個應(yīng)用都要滿足自己特定的需求,因為其商業(yè)條件、應(yīng)用場景、用戶期望,以及功能復(fù)雜性各不相同。盡管如此,如果應(yīng)用必須對用戶作出響應(yīng),那我們就必須從用戶角度來考慮可感知的處理時間這個常量。事實上,雖然生活節(jié)奏越來越快(至少我們感覺如此),但人類的感知和反應(yīng)時間則一直都沒有變過:
下面這個表格展示了Web性能社區(qū)總結(jié)的經(jīng)驗法則:必須在250 ms內(nèi)渲染頁面,或者至少提供視覺反饋,才能保證用戶不走開。如果想讓人感覺很快,就必須在幾百ms 內(nèi)響應(yīng)用戶操作。超過1s,用戶的預(yù)期流程就會中斷,心思就會向其他任務(wù)轉(zhuǎn)移,而超過10s,除非你有反饋,否則用戶基本上就會終止任務(wù)!
(點擊放大圖像)
此次的優(yōu)化工作遵循以下四個原則:
- 首屏優(yōu)先:精簡和瘦身頁面,首屏優(yōu)先展示出來;
- 惰性交互:需用戶交互的部分惰性加載;
- 惰性執(zhí)行:能不執(zhí)行的先別執(zhí)行,惰性執(zhí)行;
- 惰性滾屏:滾屏惰性加載。
遵循這四個原則,進行了優(yōu)化工作。
4. 主要優(yōu)化工作
(1)首屏優(yōu)先
為了保證首屏優(yōu)先展示,HTML文檔進行了適當精簡。
目的:盡快渲染出頁面并達到可交互的狀態(tài)。
方法:
- 如果非必須,盡量只生成首屏需要的HTML數(shù)據(jù)。
- 優(yōu)先獲取資源、提前解析。如首屏需要的CSS和JS;如果不考慮維護成本,可以把首屏需要的CSS和JS放到文檔中。
- 發(fā)現(xiàn)和優(yōu)先安排關(guān)鍵網(wǎng)絡(luò)資源,盡早分派請求并取得頁面。
- 文檔精簡后,服務(wù)端生成程序耗時短,性能才會好。
如下圖所示,列表頁的頭、面包屑、品牌區(qū)、屬性篩選區(qū)、60個商品主圖數(shù)據(jù),這些是服務(wù)端模板渲染輸出;而剩余部分是在前端JS惰性加載或生成。
(點擊放大圖像)
(2)惰性交互
惰性交互,即對需用戶交互的部分進行惰性加載。
對于三級列表頁品牌區(qū),服務(wù)端只渲染18個品牌,用戶在點更多時,AJAX異步加載其他的。對于整個屬性是篩選區(qū)服務(wù)端只渲染5行,其他行用戶在點更多時,JS從文檔嵌入資源中取到數(shù)據(jù),并渲染成HTML。這樣做可以保證服務(wù)端計算量少,提升服務(wù)端性能,減少數(shù)據(jù)傳輸。
如下圖,點“更多”時才加載更多的品牌,因為有些三級類目有非常多品牌,如果不采用這種方式,整個頁面渲染非常慢。
(點擊放大圖像)
因為需要SEO的原因,京東三級列表頁不能使用BigPipe等技術(shù)來進行更優(yōu)的處理。
(3)惰性執(zhí)行
能不執(zhí)行的先別執(zhí)行,惰性執(zhí)行。
上圖是三級列表頁最重要的商品區(qū)(商品主圖+N個關(guān)聯(lián)商品小圖),每個商品的區(qū)域都是完全一樣的;如果在服務(wù)端拼裝整個商品區(qū)域的話,尤其涉及到小圖部分,會有非常多的重復(fù)HTML元素。
(點擊放大圖像)
我們把體驗和減少頁面內(nèi)容進行了折中處理:服務(wù)端渲染輸出商品主圖部分;小圖部分通過Json數(shù)據(jù)嵌入到頁面,然后通過JS惰性執(zhí)行渲染。這樣可以很好地對頁面進行瘦身。而且小圖資源是頁面嵌入的,非異步加載;沒有網(wǎng)絡(luò)請求。因此,用戶基本感知不到異步帶來的渲染閃動問題。
下圖就是頁面嵌入的小圖Json數(shù)據(jù)。
(點擊放大圖像)
(4)惰性滾屏
三級列表頁的60個商品區(qū)域的圖片和頁尾都是當用戶向下滾動頁面時,才去加載當前屏幕中的圖片和模塊。這樣可以節(jié)省服務(wù)器帶寬和壓力,提升頁面整體渲染時間。
5. 細節(jié)優(yōu)化工作
在實際優(yōu)化過程中,還涉及到非常多的優(yōu)化細節(jié)。
將一些JS/CSS資源直接嵌入頁面
把資源嵌入文檔可以減少請求的次數(shù)。比如頁面需要的JS、CSS數(shù)據(jù)。如下圖所示:
(點擊放大圖像)
上圖中的這些JS對象,是后端渲染輸出的,因此不適合放入單獨的JS文件,直接在頁面中嵌入輸出會更好些。slaveWareList是小圖的列表對象。如果放在服務(wù)端模板渲染輸出的話,首先需要進行一些循環(huán)拼裝頁面;另外會使頁面體積變得非常大。
權(quán)衡之后決定放到前端JS渲染輸出。這樣也帶來了一些好處:
- 減輕服務(wù)端壓力,提升渲染模板性能和減少服務(wù)端執(zhí)行時間;
- 服務(wù)端不用生成HTML,文檔減少上百個div,減少頁面大小和網(wǎng)絡(luò)開銷;
- 提前放到文檔中,不用異步調(diào)用;
- 用戶基本感知不到渲染過程。
對引入的資源排定優(yōu)先次序
根據(jù)自己系統(tǒng)的業(yè)務(wù),對每種資源定優(yōu)先級:對必需的資源優(yōu)先加載,而低優(yōu)先級的請求保存在隊列中延時加載或等待必需資源加載完再加載;如:搜索推薦熱詞、頂部三個熱賣商品接口、60個主商品的圖片、價格優(yōu)先加載。而對于庫存、促銷信息、廣告詞、預(yù)售商品、店鋪信息等,延后加載。對于點擊流,廣告統(tǒng)計數(shù)據(jù)則延時兩秒再加載。
應(yīng)用JS緩存來存儲公有屬性和商品信息屬性
三級列表頁中的每個商品都是一個對象,存放在一個Map中,通過AJAX接口異步填充和維護商品的屬性。用于后續(xù)用戶交互用。同時維護成本也會降低;即頁面中用到的每個商品數(shù)據(jù)放入一個map中,如果沒有則異步加載;如果有直接使用;即這些數(shù)據(jù)是公共數(shù)據(jù)。
(點擊放大圖像)
AJAX接口最優(yōu)調(diào)用
頁面往往依賴很多的異步接口,因此要對異步接口進行壓測,找出接口的最優(yōu)調(diào)用方式。如京東三級列表頁依賴價格、庫存、廣告詞、店鋪信息等異步調(diào)用接口。而頁面有時候會出現(xiàn)多達300多個商品,如果用一個get請求把這些sku做參數(shù),性能非常慢,那么就要采用分組分批調(diào)用。如頁面商品在300個時,價格接口分六組,第一組30個,第二組30個,第三組60個,第四組60個,第五組100個,第六組100個。
DNS預(yù)解析
對可能的域名進行提前解析,避免將來HTTP請求時的DNS延遲。如對價格、庫存、圖片、單品頁等服務(wù)預(yù)解析。
(點擊放大圖像)
>
減少HTTP重定向
HTTP 重定向極費時間,特別是不同域名之間的重定向,更加費時;這里面既有額外的DNS 查詢、TCP 握手,還有其他延遲。最佳的重定向次數(shù)為零。比如三級列表頁以前是http://list.jd.com/9987-653-655.html,而現(xiàn)在是http://list.jd.com/list.html?cat=9987,653,655;在過渡期間可以重定向,但是過渡完成后就沒必要重定向了。
使用CDN(內(nèi)容分發(fā)網(wǎng)絡(luò))
把數(shù)據(jù)放到離用戶地理位置更近的地方,可以顯著減少每次TCP連接的網(wǎng)絡(luò)延遲,增大吞吐量。比如京東三級列表頁、商品詳情頁、公共JS、CSS。
傳輸壓縮過的內(nèi)容(Gzip壓縮)
傳輸前應(yīng)該壓縮應(yīng)用資源,把要傳輸?shù)淖止?jié)減至最少:確保對每種要傳輸?shù)馁Y源采用最好的壓縮手段。所有文本資源都應(yīng)該使用Gzip壓縮,然后再在客戶端與服務(wù)端間傳輸。一般來說,Gzip可以減少60%~80%的文件大小,也是一個相對簡單(只要在服務(wù)器上配置一個選項),但優(yōu)化效果較好的舉措。(對于壓縮級別,經(jīng)過不同服務(wù)器多次壓測,建議Nginx設(shè)置為1-4)
去掉不必要的資源
任何請求都不如沒有請求快,把一些非必須的或者可異步的,或者可延遲的盡量延遲請求。
在客戶端緩存資源
應(yīng)該緩存應(yīng)用資源,從而避免每次請求都發(fā)送相同的內(nèi)容。
對靜態(tài)資源CSS/JS或變化不頻繁的HTML塊,可以放到前端localstorage。因為每次都傳輸一些不變的靜態(tài)文件或者HTML,實在是太浪費了。
無狀態(tài)域名
Cookie 在很多應(yīng)用中都是常見的性能瓶頸,很多開發(fā)者都會忽略它給每次請求增加的額外負擔(dān);減少請求的HTTP首部數(shù)據(jù)(比如HTTP cookie),節(jié)省的時間相當于幾次往返的延遲時間。如列表頁依賴的價格、庫存接口,采用3.cn無狀態(tài)域名,從而減少主域下cookie傳輸。
并行處理請求和響應(yīng)
請求和響應(yīng)的排隊都會導(dǎo)致延遲,無論是客戶端還是服務(wù)器端。這一點經(jīng)常被忽視,但卻會無謂地導(dǎo)致很長延遲。
域名分區(qū)
當頁面中非常多請求都是一個域名下資源時,由于瀏覽器同時只能打開6個連接池,而且每個鏈接池是對不同域名起作用,所以很多請求一個域名會出現(xiàn)排隊現(xiàn)象。如果把這些請求域名分區(qū),讓請求并行,從而加快資源下載。如:頁面需要下載上百張圖片,對圖片進行域名分區(qū)調(diào)用。京東大部分頁面都對圖片進行了域名分區(qū)調(diào)用:
- http://img10.360buyimg.com/
- http://img11.360buyimg.com/
- http://img12.360buyimg.com/
- http://img13.360buyimg.com/
- http://img14.360buyimg.com/
拼合和連接
合并鏈接:把多個JavaScript 或CSS 文件組合為一個文件。
拼合:把多張圖片組合為一個更大的復(fù)合的圖片(CSS Sprites)。
服務(wù)端寫相關(guān)信息到header
把服務(wù)器IP后兩位寫到header,如果有問題,方便定位哪臺服務(wù)器。ups:后端路由的所有服務(wù)器都取到。把緩存命中信息或異常走兜底了,把后端運行狀態(tài)寫到header。Head-status:命中、未命中、異常等狀態(tài)。
(點擊放大圖像)
5. 降級方案和異步模塊出錯上報功能的實現(xiàn)
降級方案
- 主動降級
頁面依賴很多AJAX異步接口服務(wù),難免保證這些服務(wù)從不出錯。所以在調(diào)用這些接口服務(wù)時都提前判斷該接口開關(guān)是否開啟,如果開關(guān)關(guān)閉則不調(diào)用該接口服務(wù)。頁面不展示相關(guān)模塊。保證在一個接口服務(wù)出問題時,我們可以快速降級。
- 被動降級
當某個異步接口服務(wù)返回非200狀態(tài)碼、請求超時、數(shù)據(jù)格式不正確等異常,就會被動隱藏或不展示相應(yīng)模塊。最上面三個熱賣商品依賴的廣告服務(wù)出問題時,會把每個三級分類對應(yīng)的三個兜底商品展示出來,防止開天窗。對于其他模塊因為是商品的屬性,暫時做隱藏處理。
上報模塊錯誤
當頁面被動降級了,js就會上報該模塊,后臺程序記錄并報警。同時也會上報js運行中出錯的信息。記錄什么瀏覽器,哪個版本,什么錯誤。我們會對這些問題驗證和修改。保證每個用戶都能訪問。
Web性能監(jiān)控
為什么要做Web性能監(jiān)控,因為頁面可能放在CDN,前端JS執(zhí)行很多業(yè)務(wù)邏輯不知道運行情況,整個鏈路網(wǎng)絡(luò)偶爾不穩(wěn)定、頁面依賴的模塊和第三方異步服務(wù)多人工難以實時監(jiān)控等,這些情況請求還沒有到后端就可能出問題,所以后端監(jiān)控?zé)o能為力。
前端監(jiān)控分兩個方向:用WebKit內(nèi)核模擬瀏覽器,定時抓取設(shè)定的頁面;前端JS植入監(jiān)控。
- 用WebKit內(nèi)核模擬瀏覽器,定時抓取設(shè)定的頁面
該Web監(jiān)控項目采用一個中心服務(wù),多個終端服務(wù)來完成大量頁面抓取和校驗。
部署到全國各個機房,實時監(jiān)控頁面是否打開正常(請求超時、返回非200)、頁面HTML關(guān)鍵元素是否丟失,頁面是否出現(xiàn)亂碼等。
每個終端定時向中心服務(wù)請求需要處理的頁面URL和該頁面需要驗證的規(guī)則。如果驗證不通過,則記錄下來并報警。同時會保存現(xiàn)場(HTML文檔、頁面截圖)。
該項目在這次618起到很重要的作用,頁面出現(xiàn)任何問題,都會提前檢測出來。
- 前端JS植入監(jiān)控
該JS統(tǒng)計頁面白屏?xí)r間、首屏加載時間、每個AJAX異步方法調(diào)用耗時和請求狀態(tài)碼。
同時也會上報異步模塊降級了,JS運行中錯誤信息等。
埋點統(tǒng)計
京東列表頁的埋點主要是來統(tǒng)計用戶點擊當前頁面位置記數(shù),幫助廣告系統(tǒng)、業(yè)務(wù)、產(chǎn)品經(jīng)理后續(xù)的工作。
埋點數(shù)據(jù)上報,就是通過onclick發(fā)送AJAX請求到后端服務(wù)。
其中對于點擊后刷新當前頁面的情況,需要在新頁面記錄上次點擊的位置。因為在當前頁面點擊后上報AJAX方法還沒執(zhí)行就關(guān)閉當前窗口加載點擊后的URL了。
下圖是點擊流插件的統(tǒng)計,數(shù)據(jù)敏感不做展示,大家只看功能。
(點擊放大圖像)
6. 總結(jié)
用時
此次重構(gòu)的時間段為:2014年12月到2015年4月。
效果
京東三級列表頁從優(yōu)化到上線,已經(jīng)經(jīng)歷了兩個618和一個雙11的考驗,每天有上億的訪問量,頁面打開時間在20~80毫秒(在某些地區(qū)或低帶寬下會大于100ms)。
(點擊放大圖像)
后端方法調(diào)用tp99的性能數(shù)據(jù)如下圖所示。
(點擊放大圖像)
心得
列表頁從開始200+ms到現(xiàn)在100ms內(nèi),QPS單臺機器幾百到現(xiàn)在的近萬,頁面從1MB到現(xiàn)在200KB內(nèi),包擴后臺系統(tǒng)的拆分,邏輯算法后移、后臺實時計算等優(yōu)化。是需要有匠人的精神精雕細琢。
列表頁每周都會根據(jù)業(yè)務(wù)方和產(chǎn)品經(jīng)理的需求在開發(fā)功能。對于每個功能點都要深入思考,列出多種方案,最終選擇一個簡單、易維護、不影響系統(tǒng)性能、不降低用戶體驗的方案。這個過程要不斷思考、或請教有這方面經(jīng)驗的人、包括參考外部公司的方案。有趣的是可能晚上突發(fā)奇想就有更好的方案。
中間也遇到無數(shù)的坑。對于每次遇到各種問題,必須想方案避免再次出現(xiàn)。同時要分析Nginx日志,分析每個請求,進而對爬蟲、惡意參數(shù)訪問、惡意請求做相應(yīng)處理。這些都是前端服務(wù)必做的。當然后端服務(wù)也是非常重要的,后續(xù)會有列表頁量身打造的緩存(加速、抗大流量、多樣化兜底基礎(chǔ)數(shù)據(jù))、服務(wù)端架構(gòu)、自動降級、架構(gòu)高可用等方案。
轉(zhuǎn)載于:https://my.oschina.net/fdhay/blog/729346
總結(jié)
以上是生活随笔為你收集整理的京东前端:三级列表页持续架构优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通过NTP协议进行时间同步
- 下一篇: HTML中字体和字体图标的应用