凱叔解密京東千億商品系統(tǒng)核心架構(gòu)
商品,黃金交易流程最基礎(chǔ)、最核心的環(huán)節(jié),無商品不電商。商品數(shù)據(jù)無處不在,商家(采銷、供應(yīng)商)發(fā)布管理、供應(yīng)商下采購(gòu)單、倉(cāng)儲(chǔ)配送、促銷、搜索、商詳頁展現(xiàn)、購(gòu)物支付、財(cái)務(wù)結(jié)算、售后服務(wù)等,都離不開商品。商品信息要準(zhǔn)確傳導(dǎo)于京東整個(gè)供應(yīng)鏈的各節(jié)點(diǎn),必須要有一套穩(wěn)健、可靠的商品服務(wù)體系支撐。
原本并沒有統(tǒng)一的商品服務(wù)及存儲(chǔ)。DBA搭建了一套包含若干層級(jí)的SqlServer數(shù)據(jù)庫(kù)復(fù)制結(jié)構(gòu),各系統(tǒng)從各級(jí)從庫(kù)讀取數(shù)據(jù)。復(fù)制延遲嚴(yán)重的時(shí)候,超過12小時(shí),從庫(kù)還沒有更新,嚴(yán)重影響用戶體驗(yàn)。寫入口不止一個(gè),獲取到寫賬號(hào)就可以寫入。erp商品管理系統(tǒng)、POP商品系統(tǒng)、BIP甚至也開發(fā)了一個(gè)商品管理系統(tǒng),都在寫同一個(gè)庫(kù)的同一套表,數(shù)據(jù)一致性無法得到保障。在那個(gè)階段京東的訂單量、用戶量相對(duì)較少,基于數(shù)據(jù)庫(kù)的架構(gòu)一定程度上也能支撐日常流量,但是無法應(yīng)對(duì)大型促銷活動(dòng)。
在此背景下,2012年3月商品組從網(wǎng)站組獨(dú)立出來,孫歌臨危受命組建團(tuán)隊(duì),啟動(dòng)商品技術(shù)架構(gòu)升級(jí)項(xiàng)目。京東618年中狂歡購(gòu)物節(jié),系統(tǒng)(特別是0點(diǎn))會(huì)承受平時(shí)無法比擬的壓力。2012年之前的大促都會(huì)出現(xiàn)系統(tǒng)問題系統(tǒng)經(jīng)常出現(xiàn)問題,甚至圖書搶購(gòu)活動(dòng)時(shí)直接系統(tǒng)宕機(jī)。基于數(shù)據(jù)庫(kù)提供讀服務(wù)的架構(gòu),顯然已經(jīng)無法支撐業(yè)務(wù)前行,升級(jí)改造勢(shì)在必行。12年初NoSQL還是新鮮事物,交易架構(gòu)師龔杰開始實(shí)踐Redis,他在一封郵件中介紹自主封裝的客戶端以及API。商品團(tuán)隊(duì)開始基于redis內(nèi)存數(shù)據(jù)庫(kù)搭建商品讀服務(wù),并對(duì)開源Jedis做了深度封裝,擴(kuò)展了ShardedJedisPool,實(shí)現(xiàn)了更加細(xì)粒度的多分片連接池管理,并且將一個(gè)請(qǐng)求中命中到同一個(gè)分片的redis命令由串行改成pipeline并行執(zhí)行,性能提升較大。
架構(gòu)1.0 讀服務(wù)化
由Redis存儲(chǔ)全量商品數(shù)據(jù),作為內(nèi)存數(shù)據(jù)庫(kù)使用。商品信息變動(dòng)時(shí)增量更新,一主多備模式容災(zāi),同時(shí)全量刷新程序作為最后保障,一旦Redis中數(shù)據(jù)全部丟失,可以將商品庫(kù)中數(shù)據(jù)恢復(fù)到Redis。
交易系統(tǒng)直面用戶,為保證用戶選品、下單結(jié)算的順暢,提升用戶體驗(yàn)。交易系統(tǒng)對(duì)高性能、高可用的商品讀服務(wù)需求最迫切。此時(shí)架構(gòu)升級(jí)采取了一種“輕模式”,所謂輕是指盡量減少外部系統(tǒng)的改造,這樣更利于工作的快速推進(jìn)。首先在通知模式上,各種商品系統(tǒng)寫入Sqlserver主庫(kù)后,通過HttpHTTP服務(wù)通知Rcat -server(采取盡量做的策略,能通知就通知到,有異常也不去重試,這種策略對(duì)相關(guān)系統(tǒng)的改造最小)。Rcat-server的職責(zé)就是接收通知,之后查詢主庫(kù)中的商品信息,將其更新到Redis中。因?yàn)閷懭胂到y(tǒng)是盡量做,不保證成功的模式,因此需要補(bǔ)償機(jī)制去彌補(bǔ)遺漏。異步Worker會(huì)定期掃描數(shù)據(jù)庫(kù),把當(dāng)日更新的,從刷新到Redis中。整體架構(gòu)如圖1-1所示:
圖1-1 商品架構(gòu)V1.0
V1.0版架構(gòu)非常不完美,只是讀服務(wù)這個(gè)點(diǎn)進(jìn)行了改造,但是卻在當(dāng)年618中完美的完成了任務(wù)。618當(dāng)天像往年一樣,研發(fā)工程師售后守候在運(yùn)維同學(xué)周圍,時(shí)刻準(zhǔn)備著!整個(gè)過程波瀾不驚,只有過個(gè)別應(yīng)用過載重啟,整體非常順利扛過了大流量的考驗(yàn)。有了這個(gè)經(jīng)驗(yàn),研發(fā)工程師們開始將Rcat(應(yīng)用名稱,商品讀服務(wù))推廣,依賴Sqlserver從庫(kù)的系統(tǒng)都逐步切換到讀服務(wù)上。
架構(gòu)2.0 全面服務(wù)化
POP商品系統(tǒng)和自營(yíng)商品系統(tǒng)都是寫入Oracle,在通過異步worker將數(shù)據(jù)寫會(huì)Sqlserver。京東商品的獨(dú)特之處在于最初是自采自銷的自營(yíng)類商品,有自己的商品模型和對(duì)應(yīng)的erp管理系統(tǒng)。后續(xù)有了POP開放平臺(tái),該平臺(tái)的商品模型和系統(tǒng)是獨(dú)立搭建的,適應(yīng)于第三方商家的商品發(fā)布管理。而所有下游的系統(tǒng)(單品、搜索、訂單生產(chǎn)、倉(cāng)庫(kù)等)都是基于最初Sqlserver自營(yíng)skuSKU模型做的系統(tǒng)設(shè)計(jì)。所以POP商品有自己的Oracle存儲(chǔ),也必須京東經(jīng)過一步異步程序轉(zhuǎn)化,寫到Sqlserver商品庫(kù)中。而自營(yíng)商品系統(tǒng)在2011年架構(gòu)升級(jí)時(shí),計(jì)劃由.Net+Sqlserver的技術(shù)體系升級(jí)外Java+Oracle技術(shù)體系。
孫歌作為研發(fā)負(fù)責(zé)人,基于技術(shù)前瞻性和成本考慮,果斷決策放棄既昂貴又沒有DBA團(tuán)隊(duì)良好支持的Oracle數(shù)據(jù)庫(kù)。轉(zhuǎn)而直接基于SqlServer實(shí)現(xiàn)商品的全面服務(wù)化,相比其他系統(tǒng)的去O足足早了一年。當(dāng)時(shí)的整體思路是先實(shí)現(xiàn)服務(wù)化,再進(jìn)行存儲(chǔ)升級(jí)(Mysql集群),最終完成徹底的技術(shù)架構(gòu)升級(jí)。全面服務(wù)化過程:
(1) 全面讀服務(wù)體系:
將下游系統(tǒng)讀取的信息刷新到Redis中,由讀服務(wù)Rcat統(tǒng)一支持根據(jù)skuId查詢的需求。對(duì)于檢索需求,例如根據(jù)分類id查詢SkuSKU列表,搭建Solr索引服務(wù)。基于各系統(tǒng)的需求收集已經(jīng)當(dāng)前SQL的分析,搭建了讀服務(wù)體系,并逐步推廣,去掉各系統(tǒng)對(duì)Sqlserver數(shù)據(jù)庫(kù)的讀取依賴。
(2) 寫服務(wù)化
接管所有商品寫操作,提供商品相關(guān)的基本讀寫服務(wù),建立商品主數(shù)據(jù)中心,服務(wù)于自營(yíng)商品管理、POP平臺(tái)、圖書音像商品管理等系統(tǒng)。京東起家于3C自營(yíng)產(chǎn)品, SKU化管理。后發(fā)展的POP平臺(tái),是商品化發(fā)布管理。寫服務(wù)必須兼容兩套模型,平滑過渡。研發(fā)工程師最終設(shè)計(jì)為統(tǒng)一到商品-skuSKU模型,自營(yíng)商品與SkuSKU一對(duì)一,而POP商品與SkuSKU是一對(duì)多。自營(yíng)商品每發(fā)布一個(gè)商品有且僅有一個(gè)SKUSku,pop商家發(fā)布一個(gè)商品可以有多個(gè)SkuSKU。自營(yíng)商品溝通通過后期的顏色尺碼綁定,實(shí)現(xiàn)銷售關(guān)系捆綁。這樣展現(xiàn)給用戶的效果是一樣的,同時(shí)對(duì)于京東采銷、POP商家都可以按各自的習(xí)慣去操作商品。
寫服務(wù)設(shè)計(jì)時(shí)架構(gòu)師尤鳳凱充分考慮了擴(kuò)展性、可復(fù)用性,實(shí)現(xiàn)了模塊可配置化。基于商品介紹、擴(kuò)展屬性、規(guī)格參數(shù)、特殊屬性等基礎(chǔ)組件,可靈活組配不同的業(yè)務(wù)流程。使用了京東自主研發(fā)的SAF中間件,其支持負(fù)載均衡、自動(dòng)故障切換、流量控制、黑白名單等特性,并且在性能方面表現(xiàn)優(yōu)異。
(3) 去O:去掉自營(yíng)商品系統(tǒng)的Oracle,直接寫Sqlserver降低延遲,縮短商家發(fā)布到展現(xiàn)給最終用戶的時(shí)間。
(4) 異步引擎:建立下發(fā)服務(wù)系統(tǒng),統(tǒng)一化消息通知網(wǎng)站、WMS等下游系統(tǒng)。
最初的商品變動(dòng)時(shí),只需要通知WMS系統(tǒng), 因?yàn)椴少?gòu)入庫(kù)時(shí),倉(cāng)庫(kù)需要核實(shí)商品信息。隨著整個(gè)京東服務(wù)化進(jìn)程的推進(jìn),搜索、單品頁等系統(tǒng)都希望得到通知。因此規(guī)劃了下發(fā)服務(wù),在商品信息變動(dòng)后,異步通知這些系統(tǒng)。將商品信息入庫(kù)后,同步寫”操作日志”到Redis隊(duì)列,再由worker異步從Redis中取日志,拿到變動(dòng)變更的skuSKU,組裝信息化發(fā)MQ消息出去。此時(shí)的消息采取通用化設(shè)計(jì),例如基本信息MQ、特殊屬性MQ等。
(5) 存儲(chǔ)升級(jí):商品主數(shù)據(jù)存儲(chǔ)由Sqlserver單庫(kù),升級(jí)為MySQL集群,并進(jìn)行垂直、水平劃分, 分庫(kù)解決單庫(kù)吞吐量瓶頸,分表控制單表數(shù)據(jù)量。自主研發(fā)數(shù)據(jù)中間件,可以實(shí)現(xiàn)主庫(kù)的路由、從庫(kù)的負(fù)載均衡、故障的切換等,統(tǒng)一負(fù)責(zé)數(shù)據(jù)訪問,使得底層存儲(chǔ)規(guī)則對(duì)應(yīng)用層透明。結(jié)合當(dāng)前數(shù)據(jù)總量及增長(zhǎng)率,預(yù)計(jì)3年后達(dá)到的數(shù)據(jù)量,做存儲(chǔ)容量規(guī)劃,并且做了Pre-Sharding方式,方便后續(xù)擴(kuò)容。對(duì)于非片鍵外的查詢,使用二級(jí)路由或者搜索服務(wù)來解決。
整體架構(gòu)如圖1-2所示
圖1-2 商品架構(gòu)v2.0
隨著商品及SKU數(shù)量顯著增長(zhǎng)、TPS逐步走高。寫服務(wù)、下發(fā)服務(wù)耦合的弊端越來越突出顯現(xiàn)。如1-2圖中紅色線條形成的環(huán)狀所示,寫服務(wù)和下發(fā)服務(wù)是相互影響的。假設(shè)寫Redis變慢,會(huì)影響整體寫入性能;如果DB遇到瓶頸,反過來又回會(huì)影響到下發(fā)服務(wù),回查DB變慢,下發(fā)服務(wù)處理變慢。因此寫服務(wù)經(jīng)常會(huì)出現(xiàn)抖動(dòng),寫變慢、下發(fā)延遲。
架構(gòu)3.0平臺(tái)化、精準(zhǔn)化
2015年中開始,架構(gòu)師李帥啟動(dòng)3.0架構(gòu)升級(jí),其理念是解耦、簡(jiǎn)化。架構(gòu)越簡(jiǎn)單越好,沒接觸過的人新人很快可以上手;架構(gòu)也必須松耦合的,寫、下發(fā)、讀都可以各自做升級(jí),相互不影響,逐步提升整體性能。
用Jingobus基于從庫(kù)日志識(shí)別商品信息變動(dòng),并發(fā)送通知、刷新redis集群。異步引擎消息可配置化,商品屬性的組合對(duì)應(yīng)消息隊(duì)列。例如:搜索關(guān)注skuSKU狀態(tài)變化,那么只有skuSKU的上下柜狀態(tài)變化時(shí),才會(huì)發(fā)送消息,消息體中只包含skuSKU編號(hào)及狀態(tài)。可配置化的任務(wù)引擎,新需求響應(yīng)快、開發(fā)接近零成本;實(shí)現(xiàn)了按需發(fā)送,給消費(fèi)方減壓(例如:給wms的消息量降低80%+);并且寫系統(tǒng)解耦,整體穩(wěn)定性增強(qiáng)。在推廣過程中,還進(jìn)行跨部門技術(shù)協(xié)作,共同升級(jí)技術(shù)架構(gòu)。例如網(wǎng)站單品頁數(shù)據(jù)異構(gòu)升級(jí)項(xiàng)目,降低50%+接口交互,節(jié)約上百臺(tái)Docker。
商品服務(wù)作為超0級(jí)系統(tǒng) ,必須具有高可用性。任何影響到訂單的系統(tǒng)故障都必須在三分鐘內(nèi)解決,有了統(tǒng)一切換平臺(tái),執(zhí)行預(yù)案是可以很快的。因此發(fā)現(xiàn)問題并警報(bào)至關(guān)重要。在研發(fā)負(fù)責(zé)人趙湘建的引領(lǐng)下,商品組啟動(dòng)秒級(jí)監(jiān)控平臺(tái)的研發(fā)。內(nèi)存級(jí)監(jiān)控信息收集、合并、延遲秒級(jí)。并且實(shí)現(xiàn)了秒級(jí)監(jiān)控的平臺(tái)化,可將監(jiān)控能力輸出到其他系統(tǒng)。
期間商品讀服務(wù)也在持續(xù)進(jìn)行著升級(jí)。因?yàn)閟kuSKU數(shù)量大(數(shù)十億)且持續(xù)增長(zhǎng)(周增長(zhǎng)率約105%),Redis存儲(chǔ)集群規(guī)模也大。讀服務(wù)為其他眾多系統(tǒng)提供商品數(shù)據(jù)的查詢服務(wù),訪問量很大,特別是在618,雙11期間,所以需要多組Redis集群分擔(dān)流量。NIO的Redis客戶端,降低了連接數(shù)量;后續(xù)為解決多組Redis集群流量均衡問題,對(duì)NIO版本的客戶端做了擴(kuò)展,實(shí)現(xiàn)了多分片連接統(tǒng)一管理,使其負(fù)載均衡,并能當(dāng)某一分片宕機(jī)的情況下,自動(dòng)從集群中剔除,恢復(fù)后自動(dòng)加入集群中,達(dá)到故障自動(dòng)轉(zhuǎn)移與恢復(fù)的目的。不僅提高了集群整體的吞吐量,而且提升了可靠性。
同時(shí)因?yàn)樯唐窋?shù)量增長(zhǎng)很快,Redis集群的規(guī)模也成倍增加,為減少資源的利用,設(shè)計(jì)三級(jí)緩存功能,將最熱數(shù)據(jù)放應(yīng)用內(nèi)存中,緩存時(shí)間較短;熱數(shù)據(jù)放在規(guī)模較小的Redis集群中,全量數(shù)據(jù)放到規(guī)模較大的集群中,這樣全量數(shù)據(jù)的Redis集群OPS較少,可以減少部署組數(shù),從而減少資源利用。
圖1-3 商品架構(gòu)V3.0
京東商品系統(tǒng)在業(yè)務(wù)創(chuàng)新、數(shù)據(jù)智能化、性能及穩(wěn)定性提升方面,將持續(xù)努力提升,努力實(shí)踐讓技術(shù)改變生活的愿景。
作者:尤鳳凱, 京東商城研發(fā)-交易平臺(tái)-商品研發(fā)負(fù)責(zé)人。2010年加入京東,先后參與設(shè)計(jì)研發(fā)京東第一代監(jiān)控、消息、EDM等系統(tǒng)。12年開始致力于商品系統(tǒng)SOA化、商品系統(tǒng)的持續(xù)架構(gòu)演進(jìn)。現(xiàn)主要負(fù)責(zé)商品中臺(tái)及組件化建設(shè)。
【本文來自51CTO專欄作者張開濤的微信公眾號(hào)(開濤的博客),公眾號(hào)id: kaitao-1234567】