11.27 京東商城交易系統的演進

商城服務

京東商城交易系統的演進

如圖所示是京東交易平臺的一張大的漁網圖。從主頁面網站開始,到後面提交訂單、購物車、結算頁、訂單中心等的整個生產過程,大體可分為三個部分。第一部分是訂單提交前,就是俗稱的購物車,結算頁。第二部分是訂單預處理部分,生成訂單之後、到物流之前,還會有一些預處理過程,比如生鮮、大家電、奢侈品、易碎品等商品。第三部分是訂單履約部分。今天我講的主要內容是,交易平臺的提交以前和預處理部分。

京東商城交易系統的演進

京東交易平臺,包括單品頁的價格、庫存,購物車、促銷,結算頁的下單,再到訂單中心線。

京東商城交易系統的演進

如下圖所示,2011年京東的訂單量是30萬,2015年訂單量就已經到了3000多萬,京東的流量每年不斷地翻倍。訂單從30萬到100萬是三倍增長,實際上訪問流量的翻番,可能是10倍、50倍,甚至上百倍。比如,用戶購買東西從單品頁進入,然後查詢很多信息,包括價格、評價。商品加入購物車後,用戶會不停地比對各類商品。刷新購物車,從前端到後端所有的服務基本上都會刷新。那麼,當你刷新一次,調動服務就會承受一次動態的調用。當訂單量翻三倍的時候,實際服務訪問量最少是要翻20倍。

京東商城交易系統的演進

我見過的京東目前最大的前端流量是,一分鐘幾千萬,一個正常前端服務訪問量是在幾千萬,幾億、幾十億,一天的PV。

那為了應對如此大的調動量,每年的618、雙11,京東都做了什麼?

下面我會詳細講618、雙11備戰後面,每一年所做的不同改變。這是一個整體的大概分析,我們從哪些方面做優化,去提高系統的容災性,提高系統應對峰值流量的能力。

京東商城交易系統的演進

實際上每年京東內部的正常情況是,領導層會給出一個大概的預期值,就是希望當年的大促中,需要達到幾百億,或者幾十億的預期銷售額。那麼,根據這個銷售額,根據客單價(電商的訂單的平均價格,稱為客單價)換算成訂單量。另外在以往的618、雙11中,我們都會統計出訂單量和調用量,即前端價格需要訪問多少次,購物車需要訪問多少次,促銷引擎需要訪問多少次,整個流程需要多大的量。有了大概的方向之後,就會把具體系統的量換算出來。第一輪會做壓測,壓測分為線上壓測和線下壓測兩部分。這些都是準備工作,根據一些指標往年的增長量估算出一個預期值。

壓測

京東商城交易系統的演進

這是真正進入第一波。首先,每年的大促前,都會經歷半年業務迭代期,整個系統會有很多變更。我們會進行第一輪的壓測系統,壓測之後會知道當前線上真正能夠承載的訪問量有多大,距離預期有多遠。壓測分為線上壓測跟線下壓測。壓測場景分為讀業務和寫業務,壓測方案有集群縮減服務、模擬流量、流量洩洪。

講到壓測,先說說壓測的來歷吧。2011年時候沒有線上壓測,線下壓測也不是很全。真正引入線上壓測是在2014年,訂單量已經接近2000萬。之前的大促備戰,是通過組織架構師、優秀的管理人員,優秀的技術人員,一起去評估優化系統,因為在迭代代碼的同時,我們會知道系統哪裡容易出現問題,然後對數據庫、Web或者業務服務做一堆優化。

在2014年,訂單量到了上千萬,換算成為訪問量,每天的PV大漲,集群也更大偏大,如果還是隻依靠技術人員去優化,可能會不足。於是就衍生出壓測,我們想知道系統的極限值。這樣,當系統承受不住訪問請求的時候,我們就會知道哪裡出現瓶頸,比如,服務器的CPU、內存、連接速度等。我們通過第一輪壓測找到第一波的優化點,開始了線上的壓測。

當時第一波做線上壓測是在凌晨一兩點,把整個線上的流量剝離小部分機器上。把集群剝離出來,然後再做壓測。所有的服務器、所有的配置就是用線上完全真實的場景去做壓測,就能夠得到線上服務器在真實情況,再優化。

曾經做redis壓測,把進程綁定到單核CPU,redis是單進程程序,當時集群的性能就提升了5%。因為機器的每次CPU切換,都需要損耗資源,當時把進程直接綁定到固定的CPU上,讓它高壓下不頻繁地切換CPU進程。就這樣一個改變,性能提升了5%。當量很大的時候,真正底層細節的小改變,整個性能就會有很大的改進。這是我們從2014年引進線上壓測和線下壓測之後的一個真實感受。

京東商城交易系統的演進

壓測完之後得到容量,得到交易系統的購物車、結算頁大概承受值,之後會進行一輪優化,包括對NoSQL緩存的優化。京東在2012年的時候自建CDN網絡,Nginx層做了很多模塊加Nginx+lua的改造。應用程序層也會做很多緩存,把數據存在Java虛擬器裡面。數據層的緩存,主要有redis、 NoSQL的使用,另外會剝離出一些獨立的數據存儲。

緩存 壓縮

京東商城交易系統的演進

CDN域名切換的問題,原來外部CDN切換IP,需要15-20分鐘,整個CDN才能生效。我們的運維做了很多的改進,自建了CDN,內網VIP等等進行緩存壓縮。Nginx本身就有介質層的緩存和GZIP壓縮功能,把靜態js、CSS文件在Nginx層直接攔掉返回,這樣就節省了後面服務的服務器資源。GZIP壓縮能壓縮傳輸的文件以及數據,節省了網絡資源的開銷(GZIP壓縮主力損耗CPU,機器內部資源的平衡)。前面就直接壓縮返回圖片、文件系統等靜態資源。流量到部署集群系統時,只需要處理動態資源的計算,這樣就將動態靜態分離集中處理這些專向優化。

真正的計算邏輯,服務自身的組裝、如購物車的促銷商品、服務用戶,基本上所有資源都耗費在此。比如,連接數都會耗費在跟促銷,商品,用戶服務之間調用,這是真實的數據服務。如果不分離,你用DOS攻擊直接訪問JS,然後傳一個大的包,就會完全佔用帶寬,連接和訪問速度就會非常慢。這也是一種防護措施,在大促中會做很多緩存、壓縮這些防護。

京東商城交易系統的演進

購物車從2010年就開始Java改造,整體結構的劃分主體有,促銷引擎、商品、用戶。系統結構在2012年已經成型。到13年,加入了購物車服務的存儲。原來購物車存儲的商品是在瀏覽器端的Cookie裡的,用戶更換一臺設備,之前加入的商品就會丟失掉。為了解決這個需求,我們做了購物車服務端存儲,只要登錄,購物車存儲就會從服務端拿取。然後通過購車服務端存儲打通了手機端與PC端等的存儲結構,讓用戶在A設備加入商品,在另外一個設備也能結算,提高用戶體驗。

異步 異構

京東商城交易系統的演進

2013年之後,接入了很多其他業務,如跟騰訊合作,有微信渠道,我們會把存儲分為幾份,容量就會逐步地放大。這是異步的存儲,手機端會部署一套服務,PC端會部署一套服務,微信端會部署一套服務。就會隔離開來,互不影響。

購物車就是這麼做的。購物車整個數據異步寫的時候都是全量寫的。上一次操作可能異步沒寫成功,下一次操作就會傳導都寫成功了。不會寫丟,但是可能會有一下延時,這些數據還是會同步過來。比如,從PC端加入商品之後沒有立即同步到移動端,再勾選下購物車,購物車的存儲又會發生變更,就會直接把全部數據同步到移動端。這樣,我們的數據很少會出現丟失的情況。

異步寫的數據是進行了很多的壓縮的。第一層壓縮從前端開始,整個前端是一個接口串,到後面購物車服務,先把它壓縮為單個字母的接口串,後面又會壓縮成字節碼,使字節流真正存儲到redis層裡面。當存儲壓縮得很小的時候,性能也會提高。

緩存壓縮只是為提升縱向性能做的改進。後面還會進行橫向異步異構的改進,購物車把移動端存儲剝離出去,移動端的存儲在一組redis上,PC端的存儲在另外一組上。PC端和移動端是異步去寫,這樣相互不影響,雖然它們的數據是同步的。這是針對多中心用戶所做的一些改進。

外層的異步,是做一些不重要的服務的異步,就從購物車前端看到的地址服務、庫存狀態服務。庫存狀態服務在購物車只是一些展示,它不會影響主流層、用戶下單。真正到用戶提交的時候,庫存數據才是最準確的。這樣,我們會優先保證下單流程。

京東商城交易系統的演進

接下來講講接單的異步。提交訂單,提交一次訂單原來需要寫10多張表。當訂單量提高到一分鐘10萬的時候,系統就無法承受。我們就把整個提交訂單轉成XML,這樣只寫一張表,後面再去做異步。接單的第一步,先是把整個訂單所有信息存儲下來,然後再通過狀態機異步寫原來的10多張表數據。

京東商城交易系統的演進

關於訂單中心的異步異構,訂單中心原來都是從訂單表直接調出的。隨著體量增大,系統無法承載訪問,我們異構出訂單中心的存儲,支付臺帳存儲等。 異構出來數據都具有業務針對性存儲。數據體量會變小,這樣對整體的優化提升提供很好的基礎。

這樣的存儲隔離,對訂單狀態更新壓力也會減小,對支付的臺帳、對外部展示的性能也會提升。大家會疑問,這些數據可能會寫丟。我們從第一項提交開始,直接異步寫到訂單中心存儲,到後面訂單狀態機會補全。如果拆分不出來,後面就生產不了。也就是說,到不了訂單中心,數據生產不了,一些異步沒成功的數據就會在這個環節補全。

京東商城交易系統的演進

然後是商品的異步異構。2013年,商品團隊面臨的訪問量,已經是幾十億。如何去應對這個情況呢?很多商品數據貫穿了整個交易,包括交易的分析、各個訂單的系統都會調商品系統。我們會針對系統優化。比如,針對促銷系統調用,促銷系統主要調用特殊屬性,我們把這些屬性存到促銷系統的特有存儲。庫存系統也類推。調用的特殊屬性的方法也不一樣。譬如大家電的長寬高這些特有屬性,不像前端商品頁裡只是基本屬性。這樣就把所有的屬性異構處理,針對商品緯度、商品ID等所有數據會異構一份到庫存、促銷、單品頁,後面進行改造的時候,又將數據分A包、B包、C包。京東的業務很複雜,有自營,又有平臺數據,A包可能是基礎數據,B包可能是擴展數據,C包可能是更加偏的擴展數據。這樣,促銷系統可能調用的是B包的擴展屬性,也有可能調用的是A包的基礎屬性。單品頁訪問A包、B包,調的集群是不一樣的。這樣存儲的容量就可以提高兩倍,系統的容災承載力也會提高。

商品原來是一個單表,後來慢慢發展成為了一個全量的商品系統,包括前端、後端整個一套的流程。異步異構完了之後,系統可進行各方面的優化,這樣系統的容量也會慢慢接近預期值。然後找到系統容量的最大值,如果超過這個值,整個系統就會宕機。那麼,我們會做分流和限流,來保證系統的可用性。否則,這種大流量系統一旦倒下去,需要很長的時間才能恢復正常,會帶來很大的損失。

分流限流

在618、雙11時候,手機、筆記本會有很大力度的促銷,很多人都會去搶去刷。有很多商販利用系統去刷,系統流量就不像用戶一秒鐘點三四次,而是一分鐘可以刷到一兩百萬。怎樣預防這部分流量?我們會優先限掉系統刷的流量。

Nginx層: 通過用戶IP、Pin,等一下隨機的key進行防刷。 Web 層: 第一層,Java應用實列中單個實列每分鐘,每秒只能訪問多少次;第二層 ,業務規則防刷,每秒單用戶只能提交多少次,促銷規則令牌防刷。
從Nginx,到Web層、業務邏輯層、數據邏輯層,就會分流限流,真正落到實際上的流量是很小的,這樣就會起到保護作用,不會讓後端的存儲出現崩潰。從前面開始,可能訪問價格或者購物車的時間是10毫秒,保證20臺的機器一分鐘的流量是一百萬、兩百萬。如果是40臺機器的話,承載能力會很強,會透過Java的服務,壓倒存儲,這樣會引發更大的問題,龐大存儲一旦出現問題很難一下恢復。如果從前面開始一層一層往下限,就可以起到保護底層的作用。中間層出問題比較容易處理,如Web層,限流會消耗很多CPU,會一步步加入更多機器,這樣就能夠解決這個問題。

我們需要降級分流限流。

京東商城交易系統的演進

下面結合秒殺系統來講講如何限流分流。2014年才產生秒殺系統。當時,在同一時刻可能有1500萬人預約搶一件商品,搶到系統不能訪問。後端服務都沒有出現這些問題,有的服務費不能正常展現。後來就專為搶購設計了一個秒殺系統。正常情況下,有大批量用戶需要在同一時間訪問系統,那麼就從系統結構上分出去這些流量。秒殺系統儘量不影響主流層的入口,這樣就分離出來一部分數據。

京東商城交易系統的演進

接下來講講促銷和價格。主力調用價格的服務主要在促銷引擎,限流主要是通過購物車服務,購物車到促銷引擎又會限流,促銷引擎裡面會有令牌。比如,有5000個庫存,發50萬個令牌到前端去,肯定這5000個庫存會被搶完,不可能再把其他服務的量打到後面,這樣會保護促銷引擎,這是一種總令牌模式的保護。後面的分流性能,會分集群式、重要程度去做。另外,一些廣告的價格服務,我們會優先降級,如果出問題的話會限制。另外,有一部分刷引擎刷價格服務的數據,正常情況下是保證它正常使用,但是一旦出現問題,我們會直接把它降級,這樣就保護了真實用戶的最好體驗,而不是直接清除程序的應用。

容災降級

京東商城交易系統的演進

每次雙11活動,我們會做很多的容災和降級,有多中心交易、機房容災、業務容災等各種緯度的容災。大概統計了一下做過的一些容災方案。

京東商城交易系統的演進

首先是網絡容災。前面說到SB中間件、域名解析,我們運維自己會做了核心交換機兩層專線。這是我們運維部做的一些網絡架構圖,兩邊相互容災的一個結構。有LVS、HA、域名及解析,只是單服務掛了,通過交換機,我們可以從一個機房切換到另一個機房,因為會做一些域名的解析和切換。

京東商城交易系統的演進

應用系統相互調用容災和降級:結算的容災和降級。應用系統大部分能夠降,比如庫存狀態。如果像優惠券這些不重要的服務,備註信息,可直接降級服務,不用去訪問它,直接提交就行。在提交訂單時候,首先我們會保證必要服務,這些服務都會有很多的保護措施。每個應用裡面,應用級別、服務級別的容災,比如地址服務、庫存狀態容災可以直接先降級。到提交的時候,我們直接對庫存做限制。

京東商城交易系統的演進

應用內部的容災。庫存就是結算前面的系統應用的服務,再到細一層的我們的庫存服務,這是每一個服務的容災降級。從庫存狀態這邊的話,從網絡設備內層,有網絡容災降級。應用內部有對於預算服務的降級,預算服務會有預算庫存,原來是寫MySQL數據庫。正常情況下,預算庫存是寫MASIC預算庫,當出現問題的時候,我們會異步堆列到本地機器,裝一個程序去承載這個異步MySQL數據的落地,然後再通過Work把它寫到MySQL服務裡面。正常情況下,是雙寫MySQL、redis,當MySQL承載不住的時候,我們會把MySQL異步寫到裡面。

這裡面都會有開關係統去控制。當提交訂單產生變更的時候,才會把庫存狀態從這邊推到這個庫存狀態這邊,因為庫存狀態的調用量跟價格一樣很大。今年我們看到的最大調用量是一分鐘2600萬。這樣不可能讓它直接回原到MySQL,跟直接庫存的現實存儲裡面。通過預算系統把這個狀態從左邊算好,直接在推送過到真正的存儲,這樣就把這個存儲剝離出來,這也算一種異步異構,這樣我們會提升它的容量。

這是原來的結構,就是redis直接同步,然後直接訪問。現在把它改成是,直接讓左邊的預算服務去推送到狀態服務裡面。

監控

京東商城交易系統的演進

最後主要就是監控系統,我們運維提供了網絡監控、機器監控。

網絡監控包括我們看到的SBR,以及一些專線網絡監控,如交換機、櫃頂交換機、核心交換機的監控。

京東商城交易系統的演進

接下來是應用的系統監控。機器監控有CPU、磁盤、網絡、IO等各方面系統的監控。業務緯度的監控,有訂單量、登陸量、註冊量等的監控。京東機房微屏專線的一個網絡平臺的監控,裡面有很多專線,它們相互之間的流量是怎麼樣?圖中是我們監控系統,是機器之間的監控,包括機器直接對應的交換機、前面的櫃機交換機等的網絡監控等。

這是應用系統裡面的方法監控,後面是業務級別的監控。訂單級別的包括註冊量、庫存、域站,或者區域的訂單、金額、調用量的監控都會在這裡體現。真正到大促的時候,不可能經常去操作我們的系統,去修改它的配置。正常情況下都是去查看這些監控系統。2012年之後,監控系統一點一點積累起來。當量越來越大,機器資源越來越多之後,這些監控都會直接影響正常服務,服務用戶的質量會下降,可能20臺機器宕了一臺機器,不會影響全部效果。所以,監控的精準性是一步步慢慢提高的。

京東商城交易系統的演進


分享到:


相關文章: