億級商品詳情頁架構演進技術解密 | 高可用架構系列

億級商品詳情頁架構演進技術解密 | 高可用架構系列

 

 

--http://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=210272034&idx=1&sn=3be9d2b53c7fec88716ee8affd2515f8&scene=1&srcid=UfXZNNOVZZyZjQmp0VOh&from=groupmessage&isappinstalled=0#rdhtml

 

此文是開濤在【三體高可用架構羣】之分享內容,「三體」是爲了記念三體一書對技術人的偉大影響而冠名。前端

 

張開濤:2014年加入京東,主要負責商品詳情頁、詳情頁統一服務架構與開發工做,設計並開發了多個億級訪問量系統。工做之餘喜歡寫技術博客,有《跟我學Spring》、《跟我學Spring MVC》、《跟我學Shiro》、《跟我學Nginx+Lua開發》等系列教程,博客 http://jinnianshilongnian.iteye.com/  的訪問量超過500W。java

 

京東618的硝煙雖已散去,可開發和備戰618期間總結過的一些設計原則和遇到的一些坑還歷歷在目。伴隨着網站業務發展,需求日趨複雜多樣並隨時變化;傳統靜態化方案會遇到業務瓶頸,不能知足瞬變的需求。所以,須要一種能高性能實時渲染的動態化模板技術來解決這些問題。node

 

今夜(編者:指8/27),咱們將進行服裝品類的垂直詳情頁的AB測試和切新庫存服務的1/n流量。就此機會,和你們分享一下最近一年作的京東商品詳情頁的架構升級的心路歷程。nginx

 

商品詳情頁是什麼

商品詳情頁是展現商品詳細信息的一個頁面,承載在網站的大部分流量和訂單的入口。京東商城目前有通用版、全球購、閃購、易車、惠買車、服裝、拼購、今日抄底等許多套詳情頁模板,經過一些特殊屬性、商家類型和打標來區分,每套模板數據是同樣的,核心邏輯基本同樣,可是一些前端邏輯是有差異的。git

 

目前商品詳情頁個性化需求很是多,數據來源也是很是多的(目前統計後端有差很少數十個依賴服務),並且許多基礎服務作不了的不想作的或者說須要緊急處理的都放咱們這處理,好比一些屏蔽商品需求等。所以咱們須要一種架構能快速響應和優雅的解決這些需求問題,來了問題能在5~10分鐘內搞定。咱們這邊經還常收到一些緊急需求,好比工商的一些投訴等須要及時響應。以前架構是靜態化的,確定沒法知足這種日趨複雜和未知的需求。靜態化時作屏蔽都是經過js,因此咱們從新設計了商品詳情頁的架構。github

 

它主要包括如下三部分:web

商品詳情頁系統
負責靜的部分(整個頁面)redis

 

商品詳情頁動態服務系統和商品詳情頁統一服務系統
統一服務系統 負責動的部分,好比實時庫存。目前已經上線了幾個核心服務,今晚計劃切新庫存服務的1/n流量。
動態服務系統 負責給內網其餘系統提供一些數據服務(好比大客戶系統須要商品數據),目前商品詳情頁系統已經穩定運行半年了,目前主要給列表頁提供一些數據。數據庫

 

鍵值結構的異構數據集羣

商品主數據由於是存儲在DB中,對於一些聚合數據須要聯合查詢很是多,會致使查詢性能差的問題,所以對於鍵值類型的查詢,咱們這套異構數據很是有用。咱們此次架構的調整的主要目的是知足日趨複雜的業務需求,能及時開發業務方的需求。咱們的系統主要處理鍵值數據的邏輯,關係查詢咱們有另外一套異構系統。

 

下圖是咱們的模板頁,核心數據都是同樣的,只是展現方式和一些前端邏輯不太同樣。

 

咱們詳情頁的前端展現主要分爲這麼幾個維度:

  • 商品維度(標題、圖片、屬性等)

  • 主商品維度(商品介紹、規格參數)

  • 分類維度

  • 商家維度

  • 店鋪維度

另外還有一些實時性要求比較高的如實時價格、實時促銷、廣告詞、配送至、預售等是經過異步加載。

 

咱們目前把數據按維度化存儲,好比一些維度直接redis存,性能好。

京東商城還有一些特殊維度數據:好比套裝、手機合約機等,這些數據是主商品數據外掛的,經過異步加載來實現的邏輯。還有一些與第三方合做的,如易車,不少數據都是沒法異構的,都是直接異步加載的。目前有易車、途牛等一些公司有這種合做。

 

咱們618當天PV數億,服務器端TOP99響應時間低於38ms(此處是第1000次中第99次排名的時間,PV具體數據不便公開,但TOP99基本在40ms以內)。

上圖是咱們的一個監控圖。咱們詳情頁流量特色是離散數據,熱點少,各類爬蟲、比價軟件抓取;因此若是直接查庫,防刷沒作好,很容易被刷掛。

 

商品詳情頁發展史


這是咱們的一個架構歷史。

 

架構1.0


IIS+C#+Sql Server,最原始的架構,直接調用商品庫獲取相應的數據,扛不住時加了一層memcached來緩存數據。

 

這種方式常常受到依賴的服務不穩定而致使的性能抖動。基本發展初期都是這個樣子的,扛不住加層緩存。所以咱們設計了架構2.0。

 

架構2.0


該方案使用了靜態化技術,按照商品維度生成靜態化HTML,這就是一個靜態化方案。

主要思路:

  1. 經過MQ獲得變動通知;

  2. 經過Java Worker調用多個依賴系統生成詳情頁HTML;

  3. 經過rsync同步到其餘機器;

  4. 經過Nginx直接輸出靜態頁;

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

     

主要缺點:

  1. 假設只有分類、麪包屑變動了,那麼全部相關的商品都要重刷;

  2. 隨着商品數量的增長,rsync會成爲瓶頸;

  3. 沒法迅速響應一些頁面需求變動,大部分都是經過JavaScript動態改頁面元素。

 

以前需求沒那麼多,所以頁面變動不是很頻繁,基本沒什麼問題。可是隨着商品數量的增長這種架構的存儲容量到達了瓶頸,並且按照商品維度生成整個頁面會存在如分類維度變動就要所有刷一遍這個分類下全部信息的問題,所以咱們又改造了一版按照尾號路由到多臺機器。這種生成整個頁面的方案會存在好比只有分類信息變了,也須要把這個分類下的商品從新刷一遍。

 

架構2.1


主要思路:

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

  2. 按維度生成HTML片斷(框架、商品介紹、規格參數、麪包屑、相關分類、店鋪信息),而不是一個大HTML;

  3. 經過Nginx SSI合併片斷輸出;

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

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

 

這種方式經過尾號路由的方式分散到多臺機器擴容,而後生成HTML片斷,按需靜態化;當時咱們作閃購的時候,須要加頁頭,都是經過js搞定的。但對於大的頁面結構變動,須要全量生成。尤爲像麪包屑不同的話會很麻煩,須要生成多個版本。

 

主要缺點:

  1. 碎片文件太多,致使如沒法rsync;

  2. 機械盤作SSI合併時,高併發時性能差,此時咱們尚未嘗試使用SSD;

  3. 模板若是要變動,數億商品須要數天才能刷完;

  4. 到達容量瓶頸時,咱們會刪除一部分靜態化商品,而後經過動態渲染輸出,動態渲染系統在高峯時會致使依賴系統壓力大,抗不住;

  5. 仍是沒法迅速響應一些業務需求。

 

當時我記得印象最深的就是碎片文件太多,咱們的inode不夠了,常常要半夜去公司刪文件。由於存在刪除問題,每臺服務器並非全量,因此咱們須要一個動態生成的服務,當靜態化不存在的時候還原到動態服務;但這樣雙十一時壓力很是大,咱們依賴的系統隨時都給咱們降級。

 

架構3.0

咱們的痛點:

  1. 以前架構的問題存在容量問題,很快就會出現沒法全量靜態化,仍是須要動態渲染;(對於全量靜態化能夠經過分佈式文件系統解決該問題,這種方案沒有嘗試)

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

 

其實最痛快的是業務來講咱們要搞垂直,咱們要模塊化,咱們要個性化;這些通通很差搞,所以咱們就考慮作一版全動態的。其實思路和靜態化差很少, 數據靜態化聚合、頁面模板化。

 

咱們要考慮和要解決的問題:

  1. 能迅速響瞬變的需求,各類變態需求;

  2. 支持各類垂直化頁面改版;

  3. 頁面模塊化;

  4. AB測試;

  5. 高性能、水平擴容;

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


這是咱們新的系統:三個子系統。

主要思路:

  1. 數據變動仍是經過MQ通知;

  2. 數據異構Worker獲得通知,而後按照一些維度進行數據存儲,存儲到數據異構JIMDB集羣(JIMDB:Redis+持久化引擎,是基於Redis改造的一個加了持久化引擎的KV存儲),存儲的數據都是未加工的原子化數據,如商品基本信息、商品擴展屬性、商品其餘一些相關信息、商品規格參數、分類、商家信息等;

  3. 數據異構Worker存儲成功後,會發送一個MQ給數據同步 Worker,數據同步Worker也能夠叫作數據聚合Worker,按照相應的維度聚合數據存儲到相應的JIMDB集羣;三個維度:基本信息(基本信息+擴展屬性等的一個聚合)、商品介紹(PC版、移動版)、其餘信息(分類、商家等維度,數據量小,直接Redis存儲);

  4. 前端展現分爲兩個:商品詳情頁和商品介紹,使用Nginx+Lua技術獲取數據並渲染模板輸出。

 

思路差很少: MQ獲得變動通知,Worker刷元數據到JIMDB,前端展現系統取數據渲染模板。另外咱們當時架構的目標是詳情頁上有的數據,咱們均可以提供服務出去,主要提供單個商品的查詢服務,因此咱們把這個系統叫作動態服務系統。

該動態服務分爲前端和後端,即公網仍是內網,如目前該動態服務爲列表頁、商品對比、微信單品頁、總代等提供相應的數據來知足和支持其業務。

 

目前天天爲列表頁提供增量數據服務。微信上京東入口看到的詳情頁 也是咱們這個服務提供的數據。APP的數據暫時沒走咱們的系統,不過咱們目前系統實現的是日常流量的50倍左右,性能和流量基本不是問題。咱們詳情頁架構設計的一些原則:

  1. 數據閉環

  2. 數據維度化

  3. 拆分系統

  4. Worker無狀態化+任務化

  5. 異步化+併發化

  6. 多級緩存化

  7. 動態化

  8. 彈性化

  9. 降級開關

  10. 多機房多活

  11. 多種壓測方案

 

由於咱們這邊主要是讀服務,所以咱們架構可能偏讀爲主的設計;目前我設計的幾個系統都遵循這些原則去設計:

 

數據閉環

數據閉環,即數據的自我管理,或者說是數據都在本身系統裏維護,不依賴於任何其餘系統,去依賴化,這樣獲得的好處就是別人抖動跟我不要緊。所以咱們要先數據異構。

 

數據異構,是數據閉環的第一步,將各個依賴系統的數據拿過來,按照本身的要求存儲起來;咱們把不少數據劃分爲三個主要維度進行異構:商品信息、商品介紹和其餘信息(分類、商家、店鋪等)。

 

數據原子化處理,數據異構的數據是原子化數據,這樣將來咱們能夠對這些數據再加工再處理而響應變化的需求。咱們有了一份原子化異構數據雖然方便處理新需求,但偏偏由於第一份數據是原子化的,那麼它會很分散,前端讀取時mget的話 性能不是很好,所以咱們又作了數據聚合。

 

數據聚合,是將多個原子數據聚合爲一個大JSON數據,這樣前端展現只須要一次get,固然要考慮系統架構,好比咱們使用的Redis改造,Redis又是單線程系統,咱們須要部署更多的Redis來支持更高的併發,另外存儲的值要儘量的小。

 

數據存儲,咱們使用JIMDB,Redis加持久化存儲引擎,能夠存儲超過內存N倍的數據量,咱們目前一些系統是Redis+LMDB引擎的存儲,目前是配合SSD進行存儲;另外咱們使用Hash Tag機制把相關的數據哈希到同一個分片,這樣mget時不須要跨分片合併。分片邏輯使用的是Twemproxy,和應用端混合部署在一塊兒;減小了一層中間層,也節約一部分機器。

 

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

 

數據維度化
對於數據應該按照維度和做用進行維度化,這樣能夠分離存儲,進行更有效的存儲和使用。咱們數據的維度比較簡單:

  1. 商品基本信息,標題、擴展屬性、特殊屬性、圖片、顏色尺碼、規格參數等;
    這些信息都是商品維度的。

  2. 商品介紹信息,商品維度商家模板、商品介紹等;
    京東的商品比較特殊:自營和第三方。
    自營的商品能夠任意組合,選擇其中一個做爲主商品,所以他的商品介紹是商品維度。
    第三方的組合是固定的,有一個固定的主商品,商品介紹是主商品維度。

  3. 非商品維度其餘信息,分類信息、商家信息、店鋪信息、店鋪頭、品牌信息等;
    這些數據量不是很大,一個redis實例就能存儲。

  4. 商品維度其餘信息(異步加載),價格、促銷、配送至、廣告詞、推薦配件、最佳組合等。

 

這些數據不少部門在維護,只能異步加載;目前這些服務比較穩定,性能也不錯,咱們在把這些服務在服務端聚合,而後一次性吐出去。如今已經這麼作了幾個,好比下面這個就是在服務端聚合吐出去的狀況。

http://c.3.cn/recommend?callback=jQuery4132621&methods=accessories%2Csuit&p=103003&sku=1217499&cat=9987%2C653%2C655&lid=1&uuid=1156941855&pin=zhangkaitao1987&ck=pin%2CipLocation%2Catw%2Caview&lim=6&cuuid=1156941855&csid=122270672.4.1156941855%7C91.1440679162&c1=9987&c2=653&c3=655&_=1440679196326

這是咱們url的一些規則,methods指定聚合的服務。咱們還對系統按照其做用作了拆分。

 

拆分系統

將系統拆分爲多個子系統雖然增長了複雜性,可是能夠獲得更多的好處。好比,數據異構系統存儲的數據是原子化數據,這樣能夠按照一些維度對外提供服務;而數據同步系統存儲的是聚合數據,能夠爲前端展現提供高性能的讀取。而前端展現系統分離爲商品詳情頁和商品介紹,能夠減小相互影響;目前商品介紹系統還提供其餘的一些服務,好比全站異步頁腳服務。咱們後端仍是一個任務系統。

 

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,從而減小帶寬。

 

咱們的應用就是經過Nginx+Lua寫的,每次重啓共享緩存不丟,這點咱們受益頗多,重啓沒有抖動,另外咱們還使用使用一致性哈希(如商品編號/分類)作負載均衡內部對URL重寫提高命中率;咱們對mget作了優化,如去商品其餘維度數據,分類、麪包屑、商家等差很少8個維度數據,若是每次mget獲取性能差並且數據量很大,30KB以上;而這些數據緩存半小時也是沒有問題的,所以咱們設計爲先讀local cache,而後把不命中的再回源到remote cache獲取,這個優化減小了一半以上的remote cache流量;這個優化減小了這個數據獲取的一半流量;

 

服務端分佈式緩存,咱們使用內存+SSD+JIMDB持久化存儲。

 

動態化
咱們整個頁面是動態化渲染,輸出的數據獲取動態化,商品詳情頁:按維度獲取數據,商品基本數據、其餘數據(分類、商家信息等);並且能夠根據數據屬性,按需作邏輯,好比虛擬商品須要本身定製的詳情頁,那麼咱們就能夠跳轉走,好比全球購的須要走jd.hk域名,那麼也是沒有問題的;將來好比醫藥的也要走單獨域名。

 

模板渲染實時化,支持隨時變動模板需求;咱們目前模板變動很是頻繁,需求很是多,一個頁面8個開發。

 

重啓應用秒級化, 使用Nginx+Lua架構,重啓速度快,重啓不丟共享字典緩存數據;其實咱們有一些是Tomcat應用,咱們也在考慮使用如Tomcat+Local Redis 或 Tomcat+Nginx Local Shared Dict 作一些本地緩存,防止重啓堆緩存失效的問題。

 

需求上線速度化,由於咱們使用了Nginx+Lua架構,能夠快速上線和重啓應用,不會產生抖動;另外Lua自己是一種腳本語言,咱們也在嘗試把代碼如何版本化存儲,直接內部驅動Lua代碼更新上線而不須要重啓Nginx。

 

彈性化
咱們全部應用業務都接入了Docker容器,存儲仍是物理機;咱們會製做一些基礎鏡像,把須要的軟件打成鏡像,這樣不用每次去運維那安裝部署軟件了;將來能夠支持自動擴容,好比按照CPU或帶寬自動擴容機器,目前京東一些業務支持一分鐘自動擴容,下個月會進行彈性調度嘗試。

 

降級開關
一個前端提供服務的系統必須考慮降級,推送服務器推送降級開關,開關集中化維護,而後經過推送機制推送到各個服務器;

 

可降級的多級讀服務,前端數據集羣—->數據異構集羣—->動態服務(調用依賴系統);這樣能夠保證服務質量,假設前端數據集羣壞了一個磁盤,還能夠回源到數據異構集羣獲取數據;基本不怕磁盤壞或一些機器故障、或者機架故障。

 

開關前置化,如Nginx代替Tomcat,在Nginx上作開關,請求就到不了後端,減小後端壓力;咱們目前不少開關都是在Nginx上。

 

可降級的業務線程池隔離,從Servlet3開始支持異步模型,Tomcat7/Jetty8開始支持,相同的概念是Jetty6的Continuations。咱們能夠把處理過程分解爲一個個的事件。

 

經過這種將請求劃分爲事件方式咱們能夠進行更多的控制。如,咱們能夠爲不一樣的業務再創建不一樣的線程池進行控制:即咱們只依賴tomcat線程池進行請求的解析,對於請求的處理咱們交給咱們本身的線程池去完成;這樣tomcat線程池就不是咱們的瓶頸,形成如今沒法優化的情況。經過使用這種異步化事件模型,咱們能夠提升總體的吞吐量,不讓慢速的A業務處理影響到其餘業務處理。慢的仍是慢,可是不影響其餘的業務。咱們經過這種機制還能夠把tomcat線程池的監控拿出來,出問題時能夠直接清空業務線程池,另外還能夠自定義任務隊列來支持一些特殊的業務。

 

去年使用的是JDK7+Tomcat7 最近一個月咱們升級到了JDK8+Tomcat8+G1。

 

多機房多活
對於咱們這種核心系統,咱們須要考慮多機房多活的問題。目前是應用無狀態,經過在配置文件中配置各自機房的數據集羣來完成數據讀取。

其實咱們系統只要存儲多機房就多活了,由於系統自然就是。數據集羣採用一主三從結構,防止當一個機房掛了,另外一個機房壓力大產生抖動。

各個機房都是讀本機房的從另外每一個機房都是倆份數據,不怕由於機房忽然中斷恢復後的影響。

 

多種壓測方案
咱們在驗證系統時須要進行壓測。
線下壓測,Apache ab,Apache Jmeter,這種方式是固定url壓測,通常經過訪問日誌收集一些url進行壓測,能夠簡單壓測單機峯值吞吐量,可是不能做爲最終的壓測結果,由於這種壓測會存在熱點問題;

 

線上壓測,可使用Tcpcopy直接把線上流量導入到壓測服務器,這種方式能夠壓測出機器的性能,並且能夠把流量放大,也可使用Nginx+Lua協程機制把流量分發到多臺壓測服務器,或者直接在頁面埋點,讓用戶壓測,此種壓測方式能夠不給用戶返回內容。服務剛開始的時候大量使用tcpcopy作驗證,對於一些 新服務,若是沒法使用tcpcopy咱們就在頁面埋url讓用戶來壓。

 

另外壓測時,要考慮讀、寫、讀或寫同時壓。只壓某一種場景可能都會不真實。

 

遇到的一些問題和解決方案

SSD性能差

使用SSD作KV存儲時發現磁盤IO很是低。配置成RAID10的性能只有36MB/s;配置成RAID0的性能有130MB/s,系統中沒有發現CPU,MEM,中斷等瓶頸。一臺服務器從RAID1改爲RAID0後,性能只有~60MB/s。這說明咱們用的SSD盤性能不穩定。

 

據以上現象,初步懷疑如下幾點:SSD盤,線上系統用的三星840Pro是消費級硬盤;RAID卡設置,Write back和Write through策略(後來測試驗證,有影響,但不是關鍵);RAID卡類型,線上系統用的是LSI 2008,比較陳舊。

 

下面是使用dd作的簡單測試。

咱們現實居然使用的是民用級盤, 一個月壞幾塊很正常。後來咱們所有申請換成了INTEL企業級盤,線上用3500型號。

 

鍵值存儲選型壓測

在系統設計初期最頭痛的就是存儲選型,咱們對於存儲選型時嘗試過LevelDB、RocksDB、BeansDB、LMDB、Riak等,最終根據咱們的需求選擇了LMDB。

 

機器:2臺
配置:32核CPU、32GB內存、SSD((512GB)三星840Pro—> (600GB)Intel 3500 /Intel S3610)
數據:1.7億數據(800多G數據)、大小5~30KB左右
KV存儲引擎:LevelDB、RocksDB、LMDB,每臺啓動2個實例
壓測工具:tcpcopy直接線上導流
壓測用例:隨機寫+隨機讀

 

LevelDB壓測時,隨機讀+隨機寫會產生抖動(咱們的數據出自本身的監控平臺,分鐘級採樣)。

咱們線上一些順序寫的服務在使用leveldb,RocksDB是改造自LevelDB,對SSD作了優化,咱們壓測時單獨寫或讀,性能很是好,可是讀寫混合時就會由於歸併產生抖動。

在歸併時基本達到了咱們磁盤的瓶頸,LMDB引擎沒有大的抖動,基本知足咱們的需求。

咱們目前一些線上服務器使用的是LMDB,新機房正在嘗試公司自主研發的CycleDB引擎。目前我看到的應該不多使用LMDB引擎的。

 

數據量大時Jimdb同步不動

Jimdb數據同步時要dump數據,SSD盤容量用了50%以上,dump到同一塊磁盤容量不足。
解決方案:

  1. 一臺物理機掛2塊SSD(512GB),單掛raid0;啓動8個jimdb實例;這樣每實例差很少125GB左右;目前是掛4塊,raid0;新機房計劃8塊raid10;

  2. 目前是千兆網卡同步,同步峯值在100MB/s左右;

  3. dump和sync數據時是順序讀寫,所以掛一塊SAS盤專門來同步數據;

  4. 使用文件鎖保證一臺物理機多個實例同時只有一個dump;

  5. 後續計劃改造爲直接內存轉發而不作dump。

 

切換主從

由於是基於Redis的,目前是先作數據RDB dump而後同步。後續計劃改造爲直接內存複製,以前存儲架構是一主二從(主機房一主一從,備機房一從)切換到備機房時,只有一個主服務,讀寫壓力大時有抖動,所以咱們改造爲以前架構圖中的一主三從。

 

分片配置

以前的架構是存儲集羣的分片邏輯分散到多個子系統的配置文件中,切換時須要操做不少系統。

解決方案:

  1. 引入Twemproxy中間件,咱們使用本地部署的Twemproxy來維護分片邏輯;

  2. 使用自動部署系統推送配置和重啓應用,重啓以前暫停mq消費保證數據一致性;

  3. 用unix domain socket減小鏈接數和端口占用不釋放啓動不了服務的問題。

 

咱們都是在應用本地部署的Twemproxy,而後經過中間系統對外提供數據。

 

模板元數據存儲HTML

咱們前端應用使用的是Nginx+Lua,起初不肯定Lua作邏輯和渲染模板性能如何,就儘可能減小for、if/else之類的邏輯;經過java worker組裝html片斷存儲到jimdb,html片斷會存儲諸多問題,假設將來變了也是須要全量刷出的,所以存儲的內容最好就是元數據。

 

所以經過線上不斷壓測,最終jimdb只存儲元數據,lua作邏輯和渲染;邏輯代碼在3000行以上;模板代碼1500行以上,其中大量for、if/else,目前渲染性能夠接受。

 

線上真實流量,總體性能從TOP99 53ms降到32ms。

綁定8 CPU測試的,渲染模板的性能能夠接受。

 

 

庫存接口訪問量600W/分鐘

商品詳情頁庫存接口2014年被惡意刷,每分鐘超過600w訪問量,tomcat機器只能定時重啓;由於是詳情頁展現的數據,緩存幾秒鐘是能夠接受的,所以開啓nginx proxy cache來解決該問題,開啓後降到正常水平;咱們目前正在使用Nginx+Lua架構改造服務,數據過濾、URL重寫等在Nginx層完成,經過URL重寫+一致性哈希負載均衡,不怕隨機URL,一些服務提高了10%+的緩存命中率。

 

目前咱們大量使用內存級nginx proxy cache和nginx共享字典作數據緩存。
http://c.3.cn/recommend?callback=jQuery4132621&methods=accessories%2Csuit&p=103003&sku=1217499&cat=9987%2C653%2C655&lid=1&uuid=1156941855&pin=zhangkaitao1987&ck=pin%2CipLocation%2Catw%2Caview&lim=6&cuuid=1156941855&csid=122270672.4.1156941855%7C91.1440679162&c1=9987&c2=653&c3=655&_=1440679196326

還有咱們會對這些前端的url進行重寫,因此無論怎麼加隨機數,都不會影響咱們服務端的命中率,咱們服務端作了參數的從新拼裝和驗證。

 

微信接口調用量暴增

14年的一段時間微信接口調用量暴增,經過訪問日誌發現某IP頻繁抓取;並且按照商品編號遍歷,可是會有一些不存在的編號。

解決方案:

  1. 讀取KV存儲的部分不限流;

  2. 回源到服務接口的進行請求限流,保證服務質量。

  3. 回源到DB的或者依賴系統的,咱們也只能經過限流保證服務質量。

 

開啓Nginx Proxy Cache性能不升反降

開啓Nginx Proxy Cache後,性能降低,並且過一段內存使用率到達98%。

解決方案:

  1. 對於內存佔用率高的問題是內核問題,內核使用LRU機制,自己不是問題,不過能夠經過修改內核參數
    sysctl -w vm.extra_free_kbytes=6436787
    sysctl -w vm.vfs_cache_pressure=10000

  2. 使用Proxy Cache在機械盤上性能差能夠經過tmpfs緩存或nginx共享字典緩存元數據,或者使用SSD,咱們目前使用內存文件系統。

 

配送至讀服務因依賴太多,響應時間偏慢

配送至服務天天有數十億調用量,響應時間偏慢。
解決方案:

  1. 串行獲取變併發獲取,這樣一些服務能夠併發調用,在咱們某個系統中能提高一倍多的性能,從原來TP99差很少1s降到500ms如下;

  2. 預取依賴數據回傳,這種機制還一個好處,好比咱們依賴三個下游服務,而這三個服務都須要商品數據,那麼咱們能夠在當前服務中取數據,而後回傳給他們,這樣能夠減小下游系統的商品服務調用量,若是沒有傳,那麼下游服務再本身查一下。

 

假設一個讀服務是須要以下數據:

  1. 數據A 10ms

  2. 數據B 15ms

  3. 數據C  20ms

  4. 數據D  5ms

  5. 數據E  10ms

 

那麼若是串行獲取那麼須要:60ms;而若是數據C依賴數據A和數據B、數據D誰也不依賴、數據E依賴數據C;那麼咱們能夠這樣子來獲取數據:

那麼若是併發化獲取那麼須要:30ms;能提高一倍的性能。

 

假設數據E還依賴數據F(5ms),而數據F是在數據E服務中獲取的,此時就能夠考慮在此服務中在取數據A/B/D時預取數據F,那麼總體性能就變爲了:25ms。

 

咱們目前大量使用併發獲取和預取數據,經過這種優化咱們服務提高了差很少10ms性能。 並且能顯著減小一些依賴服務的重複調用,給他們減流。

以下服務是在抖動時的性能,老服務TOP99 211ms,新服務118ms,此處咱們主要就是併發調用+超時時間限制,超時直接降級。

 

網絡抖動時,返回502錯誤

Twemproxy配置的timeout時間太長,以前設置爲5s,並且沒有分別針對鏈接、讀、寫設置超時。後來咱們減小超時時間,內網設置在150ms之內,當超時時訪問動態服務。對於讀服務的話,應該設置合理的超時時間,好比超時了直接降級。

 

 

機器流量太大

2014年雙11期間,服務器網卡流量到了400Mbps,CPU30%左右。緣由是咱們全部壓縮都在接入層完成,所以接入層再也不傳入相關請求頭到應用,隨着流量的增大,接入層壓力過大,所以咱們把壓縮下方到各個業務應用,添加了相應的請求頭,Nginx GZIP壓縮級別在2~4吞吐量最高;應用服務器流量降了差很少5倍;目前正常狀況CPU在4%如下。

 

由於以前壓縮都是接入層作的,後來由於接入的服務太多,所以咱們決定在各應用去作。

一些總結

  • 數據閉環

  • 數據維度化

  • 拆分系統

  • Worker無狀態化+任務化

  • 異步化+併發化

  • 多級緩存化

  • 動態化

  • 彈性化

  • 降級開關

  • 多機房多活

  • 多種壓測方案

  • Nginx接入層線上灰度引流

  • 接入層轉發時只保留有用請求頭

  • 使用不須要cookie的無狀態域名(如c.3.cn),減小入口帶寬

  • Nginx Proxy Cache只緩存有效數據,如託底數據不緩存

  • 使用非阻塞鎖應對local cache失效時突發請求到後端應用(lua-resty-lock/proxy_cache_lock)

  • 使用Twemproxy減小Redis鏈接數

  • 使用unix domain socket套接字減小本機TCP鏈接數

  • 設置合理的超時時間(鏈接、讀、寫)

  • 使用長鏈接減小內部服務的鏈接數

  • 去數據庫依賴(協調部門遷移數據庫是很痛苦的,目前內部使用機房域名而不是ip),服務化

  • 客戶端同域鏈接限制,進行域名分區:c0.3.cn c1.3.cn,若是將來支持HTTP/2.0的話,就再也不適用了。

     

Q&A

Q1:對於依賴服務的波動,致使咱們系統的不穩定,咱們是怎麼設計的?
咱們的數據源有三套:前端數據集羣 該數據每一個機房有兩套,目前兩個機房。數據異構集羣同上動態服務(調用依賴系統)。

  1. 設置好超時時間,尤爲鏈接超時,內網咱們通常100ms左右;

  2. 每一個機房讀從、若是從掛了降級讀主;

  3. 若是前端集羣掛了,咱們會讀取動態服務(一、先mget原子數據異構集羣;二、失敗了讀依賴系統)。

 

Q2:靜態化屏蔽經過js是怎麼作的?

  1. 緊急上線js,作跳轉;

  2. 上線走流程須要差很少10分鐘,而後還有清理CDN緩存,用戶端還有本地緩存沒法清理;

  3. 咱們目前文件都放到咱們的自動部署上,出問題直接修改,而後同步上去,重啓nginx搞定。

 

Q3:內網的服務經過什麼方式提供?
我偏好使用HTTP,目前也在學習HTTP2.0,有一些服務使用咱們本身開發相似於DUBBO的服務化框架,以前的版本就是DUBBO改造的。

 

Q4:對於mq 的處理若是出現異常是怎麼發現和處理的?

  1. MQ,咱們目前是接收下來存Redis,而後會寫一份到本地磁盤文件;

  2. 咱們會把消息存一天的;

  3. 通常出問題是投訴,咱們會緊急回滾消息到一天中的某個時間點。

 

Q5:對於模板這塊,有作預編譯處理?或者直接使用Lua寫模板嗎?

  1. luajit,相似於java jit;

  2. lua直接寫模板。

 

Q6: jimdb可否介紹下,特性是什麼,爲何選用?

  1. jimdb就是咱們起的一個名字,以前版本就是redis+lmdb持久化引擎,作了持久化;

  2. 咱們根據當時的壓測結果選擇的,按照對比結果選擇的,咱們當時也測了豆瓣的beansdb,性能很是好,就是須要按期人工歸併。

 

Q7:諮詢下對於價格這類敏感數據,前端有緩存麼?仍是都靠價格服務扛?

  1. 價格前端不緩存;

  2. 價格實時同步到Nginx+Lua本地的redis集羣(大內存);

  3. 不命中的纔回源到tomcat查主redis/DB。

價格數據也是經過MQ獲得變動存儲到本地redis的,nginx+lua直接讀本機redis,性能沒的說。

 

Q8:庫存和價格同樣的模式處理嗎?
前端展現庫存不是,咱們作了幾秒的服務端緩存。

 

Q9:github裏有一個開源的基於lmdb的redis 大家用的這個嗎?
咱們內部本身寫的,有一版基於LevelDB的,我記得github上叫ardb。

 

Q10:看測試條件說測試的是大小5~30KB左右的數據,有沒有測試過更大文件lmdb的表現?
這個沒有,咱們的數據都是真實數據,最大的有50KB左右的,可是分佈比較均勻;當時還考慮壓縮,可是發現沒什麼性能問題,就沒有壓縮作存儲了。

 

Q11:關於redis緩存,是每一個子系統擁有本身的一套緩存;仍是使用統一的緩存服務?是否有進行過對比測試?(看到又說使用單機緩存防止服務掛掉,影響總體服務)

  1. 咱們公司有統一的緩存服務接入並提供運維;

  2. 咱們的服務本身運維,由於咱們是多機房從,每機房讀本身的從,目前不支持這種方式;

  3. (看到又說使用單機緩存防止服務掛掉,影響總體服務)這個主要是降級+備份+回源解決。

 

Q12: 「咱們目前一些線上服務器使用的是LMDB,其餘一些正在嘗試公司自主研發的CycleDB引擎」。 開始自主研發,這個是因爲lmdb有坑仍是處於別的考慮?

  1. 寫放大問題;

  2. 掛主從須要dump整個文件進行同步。

 

想和羣內專家繼續交流電商高可用架構,請關注公衆號後,回覆arch,申請進羣。

相關文章
相關標籤/搜索