1. 程式人生 > >大促訂單、PV雙線破億,解密京東商城交易系統的演進之路

大促訂單、PV雙線破億,解密京東商城交易系統的演進之路

京東商城交易系統

本文根據京東商城交易平臺的楊超在“第一期蝴蝶沙龍:揭祕618電商大促背後的高併發架構”會議上的演講整理而成。

大家好!我是來自京東商城交易平臺的楊超,今天特別高興能夠來給大家做這個分享。我是 2011 年加入京東,5 年中我經歷了不少技術架構的演進,也看到了不少變化。這次分享首先介紹京東商城的服務、京東交易結構,然後介紹針對618備戰,我們做的一些事情,以及從2011年到現在,京東交易平臺經歷的變化。

京東商城交易系統

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

商城服務

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

如下圖所示,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多張表資料。

非同步異構

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

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

1非同步異構

然後是商品的非同步異構。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臺機器宕了一臺機器,不會影響全部效果。所以,監控的精準性是一步步慢慢提高的。

監控系統

文:楊超

文章出處:InfoQ