億級商品詳情頁架構演進技術解密(上)

商品詳情頁是什麼

商品詳情頁是展示商品詳細資訊的一個頁面,承載在網站的大部分流量和訂單的入口。京東商城目前有通用版、全球購、閃購、易車、惠買車、服裝、拼購、今日抄底等許多套模板。各套模板的元資料是一樣的,只是展示方式不一樣。目前商品詳情頁個性化需求非常多,資料來源也是非常多的,而且許多基礎服務做不了的都放我們這,因此我們需要一種架構能快速響應和優雅的解決這些需求問題。因此我們重新設計了商品詳情頁的架構,主要包括三部分:商品詳情頁系統、商品詳情頁統一服務系統和商品詳情頁動態服務系統;商品詳情頁系統負責靜的部分,而統一服務負責動的部分,而動態服務負責給內網其他系統提供一些資料服務。

億級商品詳情頁架構演進技術解密(上)

億級商品詳情頁架構演進技術解密(上)

億級商品詳情頁架構演進技術解密(上)

商品詳情頁前端結構

前端展示可以分為這麼幾個維度:商品維度(標題、圖片、屬性等)、主商品維度(商品介紹、規格引數)、分類維度、商家維度、店鋪維度等;另外還有一些實時性要求比較高的如實時價格、實時促銷、廣告詞、配送至、預售等是透過非同步載入。

億級商品詳情頁架構演進技術解密(上)

億級商品詳情頁架構演進技術解密(上)

京東商城還有一些特殊維度資料:比如套裝、手機合約機等,這些資料是主商品資料外掛的。

我們的效能資料

618當天PV數億,618當天伺服器端響應時間<38ms。此處我們用的是第1000次中第99次排名的時間。

億級商品詳情頁架構演進技術解密(上)

單品頁流量特點

離散資料,熱點少,各種爬蟲、比價軟體抓取。

單品頁技術架構發展

億級商品詳情頁架構演進技術解密(上)

架構1.0

億級商品詳情頁架構演進技術解密(上)

IIS+C#+Sql Server,最原始的架構,直接呼叫商品庫獲取相應的資料,扛不住時加了一層memcached來快取資料。這種方式經常受到依賴的服務不穩定而導致的效能抖動。

架構2.0

億級商品詳情頁架構演進技術解密(上)

該方案使用了靜態化技術,按照商品維度生成靜態化HTML。主要思路:

1、透過MQ得到變更通知;

2、透過Java Worker呼叫多個依賴系統生成詳情頁HTML;

3、透過rsync同步到其他機器;

4、透過Nginx直接輸出靜態頁;

5、接入層負責負載均衡。

該方案的主要缺點:

1、假設只有分類、麵包屑變更了,那麼所有相關的商品都要重刷;

2、隨著商品數量的增加,rsync會成為瓶頸;

3、無法迅速響應一些頁面需求變更,大部分都是透過JavaScript動態改頁面元素。

隨著商品數量的增加這種架構的儲存容量到達了瓶頸,而且按照商品維度生成整個頁面會存在如分類維度變更就要全部刷一遍這個分類下所有資訊的問題,因此我們又改造了一版按照尾號路由到多臺機器。

億級商品詳情頁架構演進技術解密(上)

主要思路:

1、容量問題透過按照商品尾號做路由分散到多臺機器,按照自營商品單獨一臺,第三方商品按照尾號分散到11臺;

2、按維度生成HTML片段(框架、商品介紹、規格引數、麵包屑、相關分類、店鋪資訊),而不是一個大HTML;

3、透過Nginx SSI合併片段輸出;

4、接入層負責負載均衡;

5、多機房部署也無法透過rsync同步,而是使用部署多套相同的架構來實現。

該方案主要缺點:

1、碎片檔案太多,導致如無法rsync;

2、機械盤做SSI合併時,高併發時效能差,此時我們還沒有嘗試使用SSD;

3、模板如果要變更,數億商品需要數天才能刷完;

4、到達容量瓶頸時,我們會刪除一部分靜態化商品,然後透過動態渲染輸出,動態渲染系統在高峰時會導致依賴系統壓力大,抗不住;

5、還是無法迅速響應一些業務需求。

我們的痛點

1、之前架構的問題存在容量問題,很快就會出現無法全量靜態化,還是需要動態渲染;不過對於全量靜態化可以透過分散式檔案系統解決該問題,這種方案沒有嘗試;

2、最主要的問題是隨著業務的發展,無法滿足迅速變化、還有一些變態的需求。

架構3.0

我們要解決的問題:

1、能迅速響瞬變的需求,和各種變態需求;

2、支援各種垂直化頁面改版;

3、頁面模組化;

4、AB測試;

5、高效能、水平擴容;

6、多機房多活、異地多活。

億級商品詳情頁架構演進技術解密(上)

主要思路:

1、資料變更還是透過MQ通知;

2、資料異構Worker得到通知,然後按照一些維度進行資料儲存,儲存到資料異構JIMDB叢集(JIMDB:Redis+持久化引擎),儲存的資料都是未加工的原子化資料,如商品基本資訊、商品擴充套件屬性、商品其他一些相關資訊、商品規格引數、分類、商家資訊等;

3、資料異構Worker儲存成功後,會發送一個MQ給資料同步Worker,資料同步Worker也可以叫做資料聚合Worker,按照相應的維度聚合資料儲存到相應的JIMDB叢集;三個維度:基本資訊(基本資訊+擴充套件屬性等的一個聚合)、商品介紹(PC版、移動版)、其他資訊(分類、商家等維度,資料量小,直接Redis儲存);

4、前端展示分為兩個:商品詳情頁和商品介紹,使用Nginx+Lua技術獲取資料並渲染模板輸出。

另外我們目前架構的目標不僅僅是為商品詳情頁提供資料,只要是Key-Value獲取的而非關係的我們都可以提供服務,我們叫做動態服務系統。

億級商品詳情頁架構演進技術解密(上)

該動態服務分為前端和後端,即公網還是內網,如目前該動態服務為列表頁、商品對比、微信單品頁、總代等提供相應的資料來滿足和支援其業務。

詳情頁架構設計原則

1、資料閉環

2、資料維度化

3、拆分系統

4、Worker無狀態化+任務化

5、非同步化+併發化

6、多級快取化

7、動態化

8、彈性化

9、降級開關

10、多機房多活

11、多種壓測方案

資料閉環

億級商品詳情頁架構演進技術解密(上)

資料閉環即資料的自我管理,或者說是資料都在自己系統裡維護,不依賴於任何其他系統,去依賴化;這樣得到的好處就是別人抖動跟我沒關係。

資料異構,是資料閉環的第一步,將各個依賴系統的資料拿過來,按照自己的要求儲存起來;

資料原子化,資料異構的資料是原子化資料,這樣未來我們可以對這些資料再加工再處理而響應變化的需求;

資料聚合,將多個原子資料聚合為一個大JSON資料,這樣前端展示只需要一次get,當然要考慮系統架構,比如我們使用的Redis改造,Redis又是單執行緒系統,我們需要部署更多的Redis來支援更高的併發,另外儲存的值要儘可能的小;

資料儲存,我們使用JIMDB,Redis加持久化儲存引擎,可以儲存超過記憶體N倍的資料量,我們目前一些系統是Redis+LMDB引擎的儲存,目前是配合SSD進行儲存;另外我們使用Hash Tag機制把相關的資料雜湊到同一個分片,這樣mget時不需要跨分片合併。

我們目前的異構資料時鍵值結構的,用於按照商品維度查詢,還有一套異構時關係結構的用於關係查詢使用。

詳情頁架構設計原則 / 資料維度化

對於資料應該按照維度和作用進行維度化,這樣可以分離儲存,進行更有效的儲存和使用。我們資料的維度比較簡單:

1、商品基本資訊,標題、擴充套件屬性、特殊屬性、圖片、顏色尺碼、規格引數等;

2、商品介紹資訊,商品維度商家模板、商品介紹等;

3、非商品維度其他資訊,分類資訊、商家資訊、店鋪資訊、店鋪頭、品牌資訊等;

4、商品維度其他資訊(非同步載入),價格、促銷、配送至、廣告詞、推薦配件、最佳組合等。

拆分系統

億級商品詳情頁架構演進技術解密(上)

將系統拆分為多個子系統雖然增加了複雜性,但是可以得到更多的好處,比如資料異構系統儲存的資料是原子化資料,這樣可以按照一些維度對外提供服務;而資料同步系統儲存的是聚合資料,可以為前端展示提供高效能的讀取。而前端展示系統分離為商品詳情頁和商品介紹,可以減少相互影響;目前商品介紹系統還提供其他的一些服務,比如全站非同步頁尾服務。

Worker無狀態化+任務化

億級商品詳情頁架構演進技術解密(上)

1、資料異構和資料同步Worker無狀態化設計,這樣可以水平擴充套件;

2、應用雖然是無狀態化的,但是配置檔案還是有狀態的,每個機房一套配置,這樣每個機房只讀取當前機房資料;

3、任務多佇列化,等待佇列、排重佇列、本地執行佇列、失敗佇列;

4、佇列優先順序化,分為:普通佇列、刷資料佇列、高優先順序佇列;例如一些秒殺商品會走高優先順序佇列保證快速執行;

5、副本佇列,當上線後業務出現問題時,修正邏輯可以回放,從而修復資料;可以按照比如固定大小佇列或者小時佇列設計;

6、在設計訊息時,按照維度更新,比如商品資訊變更和商品上下架分離,減少每次變更介面的呼叫量,透過聚合Worker去做聚合。

非同步化+併發化

我們系統大量使用非同步化,透過非同步化機制提升併發能力。首先我們使用了訊息非同步化 進行系統解耦合,透過訊息通知我變更,然後我再呼叫相應介面獲取相關資料;之前老系統使用同步推送機制,這種方式系統是緊耦合的,出問題需要聯絡各個負責人重新推送還要考慮失敗重試機制。資料更新非同步化 ,更新快取時,同步呼叫服務,然後非同步更新快取。可並行任務併發化, 商品資料系統來源有多處,但是可以併發呼叫聚合,這樣本來序列需要1s的經過這種方式我們提升到300ms之內。非同步請求合併,非同步請求做合併,然後一次請求呼叫就能拿到所有資料。前端服務非同步化/聚合,實時價格、實時庫存非同步化, 使用如執行緒或協程機制將多個可併發的服務聚合。非同步化還一個好處就是可以對非同步請求做合併,原來N次呼叫可以合併為一次,還可以做請求的排重。

多級快取化

瀏覽器快取,當頁面之間來回跳轉時走local cache,或者開啟頁面時拿著Last-Modified去CDN驗證是否過期,減少來回傳輸的資料量;

CDN快取,使用者去離自己最近的CDN節點拿資料,而不是都回源到北京機房獲取資料,提升訪問效能;

服務端應用本地快取,我們使用Nginx+Lua架構,使用HttpLuaModule模組的shared dict做本地快取( reload不丟失)或記憶體級Proxy Cache,從而減少頻寬;

另外我們還使用使用一致性雜湊(如商品編號/分類)做負載均衡內部對URL重寫提升命中率;

我們對mget做了最佳化,如去商品其他維度資料,分類、麵包屑、商家等差不多8個維度資料,如果每次mget獲取效能差而且資料量很大,30KB以上;而這些資料快取半小時也是沒有問題的,因此我們設計為先讀local cache,然後把不命中的再回源到remote cache獲取,這個最佳化減少了一半以上的remote cache流量;

服務端分散式快取,我們使用記憶體+SSD+JIMDB持久化儲存。

動態化

資料獲取動態化,商品詳情頁:按維度獲取資料,商品基本資料、其他資料(分類、商家資訊等);而且可以根據資料屬性,按需做邏輯,比如虛擬商品需要自己定製的詳情頁,那麼我們就可以跳轉走,比如全球購的需要走jd。hk域名,那麼也是沒有問題的;

模板渲染實時化,支援隨時變更模板需求;

重啟應用秒級化,使用Nginx+Lua架構,重啟速度快,重啟不丟共享字典快取資料;

需求上線速度化,因為我們使用了Nginx+Lua架構,可以快速上線和重啟應用,不會產生抖動;另外Lua本身是一種指令碼語言,我們也在嘗試把程式碼如何版本化儲存,直接內部驅動Lua程式碼更新上線而不需要重啟Nginx。

彈性化

我們所有應用業務都接入了Docker容器,儲存還是物理機;我們會製作一些基礎映象,把需要的軟體打成映象,這樣不用每次去運維那安裝部署軟體了;未來可以支援自動擴容,比如按照CPU或頻寬自動擴容機器,目前京東一些業務支援一分鐘自動擴容。

降級開關

推送伺服器推送降級開關,開關集中化維護,然後透過推送機制推送到各個伺服器;

可降級的多級讀服務,前端資料叢集——->資料異構叢集——->動態服務(呼叫依賴系統);這樣可以保證服務質量,假設前端資料叢集壞了一個 磁碟,還可以回源到資料異構叢集獲取資料;

開關前置化,如Nginx——àTomcat,在Nginx上做開關,請求就到不了後端,減少後端壓力;

可降級的業務執行緒池隔離,從Servlet3開始支援非同步模型,Tomcat7/Jetty8開始支援,相同的概念是Jetty6的Continuations。我們可以把處理過程分解為一個個的事件。透過這種將請求劃分為事件方式我們可以進行更多的控制。如,我們可以為不同的業務再建立不同的執行緒池進行控制:即我們只依賴tomcat執行緒池進行請求的解析,對於請求的處理我們交給我們自己的執行緒池去完成;這樣tomcat執行緒池就不是我們的瓶頸,造成現在無法最佳化的狀況。透過使用這種非同步化事件模型,我們可以提高整體的吞吐量,不讓慢速的A業務處理影響到其他業務處理。慢的還是慢,但是不影響其他的業務。我們透過這種機制還可以把tomcat執行緒池的監控拿出來,出問題時可以直接清空業務執行緒池,另外還可以自定義任務佇列來支援一些特殊的業務。

億級商品詳情頁架構演進技術解密(上)

多機房多活

應用無狀態,透過在配置檔案中配置各自機房的資料叢集來完成資料讀取。

億級商品詳情頁架構演進技術解密(上)

資料叢集採用一主三從結構,防止當一個機房掛了,另一個機房壓力大產生抖動。

億級商品詳情頁架構演進技術解密(上)