京東三級列表頁持續架構優化:Golang + Lua (OpenResty) 最佳實踐
分類列表入口
分類列表入口,可以通過京東首頁首屏左側導航進入,是用戶購買商品的幾大入口之一。
分類列表,展示各個分類的商品,有綜合排序、價格排序、銷量排序、上架時間排序、圖書還有出版時間排序。可以按照品牌、價格和各種擴展屬性篩選出想要的商品。下圖以空調列表為例。
分類列表特點
-
分類多,全站大概幾千個分類;
-
商品多,每個分類商品多,有的分類達上千萬的商品;
-
需求多樣化,不同分類需求不一樣,例如大家電、圖書需求各不一樣;
-
請求量大,實時性要求高。
舊架構
舊架構,前端是用nodejs做模板渲染,后端服務是調用搜索接口。舊架構缺點:
-
響應時間比較長;
-
因為是搜索返回的數據,數據二次加工不方便。
升級新架構
* 新架構設計目標
-
分布式,數據可以做多個分片,服務各層可以做到水平擴容;
-
高可用,雙機房雙活部署;
-
響應迅速;
-
數據閉環,線上服務主要數據不依賴于外部API;
-
運維便捷,方便切換集群,方便分類管理配置;
-
數據提升,通過優化排序算法,提升GMV、訂單轉化率、客單價等。
* 新架構
新架構功能模塊如上圖所示:
-
頁面渲染:采用OpenResty(Nginx+Lua)來作模板渲染,方便頁面邏輯的調整;
-
業務處理:采用golang,所有的篩選、過濾邏輯都是在這一層處理的;
-
數據異構:頁面渲染需要相關的數據、過濾篩選需要的數據,都是通過異構過來的;
-
消息處理:通過接入MQ消息,可以實時處理商品上下架、庫存更新、價格修改等消息;
-
質量分計算:通過大數據平臺計算商品質量分,為綜合排序提供依據;
-
配置管理中心:負責后臺調度、分類配置等。
新架構線上流程如下圖所示
新架構離線數據流如下圖所示
其中:
-
數據集市,使用的是京東的大數據平臺;
-
JSS,是京東自研分布式文件存儲系統;
-
JIMDB,是京東自研KV存儲系統,可當分布式Redis使用。
詳解各個模塊
* 質量分計算
由于每個分類的商品非常多,個別分類達千萬量級的SKU,而用戶瀏覽的SKU有限,我們需要將用戶最可能買商品排在前面;為每個分類的所有sku進行質量分計算,涉及到幾十個指標(包括銷量、評價、瀏覽、轉化率等);根據質量分的高低進行排序;由于涉及數據量很大,所有計算都在大數據平臺完成;將計算結果推送到JSS。
由于還有一些特殊規則,例如品牌穿插、店鋪穿插、特殊排序等,這些規則的實現是通過worker實現,讀取jss,并進行特殊規則處理。將處理后的數據推送到MYSQL。
* 異構服務
異構服務主要是異構過濾和展示需要的商品數據;調用外部各個接口,形成一張商品寬表。如下圖所示:
* 業務處理子系統
上圖展示了列表各種篩選邏輯,排序邏輯。
業務處理子系統提供前端所需要的所有過濾篩選接口,以及展示數據。該系統采用golang開發,所有篩選數據都存在內存中,提高檢索速度;展示的數據都放在jimdb中,目的減少占用內存大小,縮短golang的GC時間。下圖展示了內存中存儲的數據。
* 消息處理系統
該系統接收處理相關消息(商品變更,上下架,價格變更,庫存變更),并實時更新到線上,如下圖所示:
* 頁面展示子系統
頁面展示子系統,采用Nginx+Lua實現,負責模板的渲染,如下圖所示。
為了提高頁面的渲染速度,有一部分頁面采用異步渲染,例如:頁面小圖聚合的可以讓js渲染小圖;超過5個的擴展屬性,讓js異步渲染。頁面需要的價格數據、庫存數據、廣告數據,采用異步加載;保證這些數據的實時性。
頁面渲染優化:
-
HTML文檔精簡,越簡單渲染越快,性能越好;例如:頁面小圖聚合的可以讓js渲染小圖;超過5個的擴展屬性,讓js 異步渲染;
-
懶加載數據,例如:滾屏加載圖片和頁尾;
-
資源加載排序,對每種資源定優先級,對必需的資源優先加載,而低優先級的請求保存在隊列中延時加載或等必需資源加載完再加載。例如:搜索推薦熱詞、頂部三個熱賣商品接口、價格優先加載。對于庫存、促銷信息、廣告詞、預售商品、店鋪信息等,延后加載; 對于點擊流,廣告統計數據則延時兩秒再加載;
Golang+Lua(OpenResty)的應用
* Golang–遇到的坑
-
JSON的序列化性能低下:Golang內置的encoding/json、encoding/gob,采用ffjson;
-
GC問題:減少內存對象。減少對象申請,兩個作用:減少內存使用,減少內存碎片;
-
字符串拼接:盡量使用byte數組,不要用String,由于String會創建新對象;
-
Go占用OS內存釋放慢:執行:debug.freeOSMemory();
-
Goroutine閃退:goroutine閃退,導致應用進程閃退,異常捕獲;
-
并發處理map:必須加讀寫鎖(sync.RWMutex)。
* 選擇Lua(OpenResty)
-
Lua:輕量級、協程、嵌入式、開發效率高;
-
OpenResty:OpenResty將Nginx核心、LuaJIT、許多有用的Lua庫和Nginx第三方模塊打包在一起的web應用開發框架。
** 模板渲染
使用的模板引擎https://github.com/bungle/lua-resty-template。Nginx配置如下所示。
模板如下所示。
** 緩存
緩存:
-
為后端服務異常提供托底數據;
-
當流量太大時,可以開啟緩存,減少后端服務壓力。
緩存流程:
-
解析url,對url做hash,得到相應的key,從后端服務獲取數據,如果數據完整,則渲染模版,將對應的數據放入對應的緩存,并將key放入keycache,并設置緩存時間;
-
頁面緩存是永不過期的,當key過期時,主動替換掉;
-
為什么分為兩類緩存:firstpage cache只緩存每個分類首頁的數據,這樣可以緩存全部分類的首頁,保證所有分類都有托底數據。Otherpage cache 緩存除首頁以外頁面,這樣保證熱點數據都在緩存中。如果超過容量,通過lru淘汰;
-
為什么每類緩存多個分片:因為lua_shared_dict存在自旋鎖,單片讀寫壓力大時,會有一定的瓶頸,因此采用多個分片,每個分片大小設置,根據具體緩存數據來定;
-
Firstpage和otherpage 是緩存在每臺nginx服務器上,緩存的內容有限;
-
Redis緩存,可以集中緩存,能夠緩存更多的數據;
** 異常處理
異常處理分為兩層托底,保證每層報錯,均可對異常進行處理,無5xx等錯誤,提高用戶體驗,第一層托底,展示各個分類首頁的緩存;第二層托底,跳轉京東首頁。
Lua執行問題,通過nginx配置error_page,進入異常處理。接口響應問題,通過ngx.exec內部跳轉,進入異常處理。
注:error_page默認只匹配一次,匹配多次需配置recursive_error_pageson;
ngx.exec為內部跳轉,類似于流水線,數據流動方向單一,無額外http請求。
新版性能
* 頁面渲染性能
頁面響應時間:模板渲染+業務篩選接口(go),平均在30ms左右,tp99在80ms以內,提高6倍以上;頁面渲染(NGINX+LUA)TPS,在并發100時,16核單機在3500筆/秒,提高10倍左右。
* 業務篩選接口(GO)性能
業務篩選接口(GO):平均在10ms以內,tp99在50ms左右,響應時間提高6倍以上。
來自:http://mp.weixin.qq.com/s?__biz=MzIwODA4NjMwNA==&mid=2652898074&idx=1&sn=d9770ca7b5f5707b041cb1568d8a19a4&chksm=8cdcd755bbab5e43d52d938644e0b754545c84d718eaafe2330ea2f6778c0b416bc5836ad2b3&mpshare=1&scene=1&srcid=1121RtxVlJ77tySDxb7hxgax&from=groupmessage