京东订单系统高可用架构及演变过程
京東到家是達(dá)達(dá)集團(tuán)旗下中國最大的本地即時(shí)零售平臺(tái)之一,目標(biāo)就是實(shí)現(xiàn)一個(gè)小時(shí)配送到家的業(yè)務(wù)。一直到 2019 年京東到家覆蓋 700 個(gè)縣區(qū)市,合作門店近 10 萬家,服務(wù)數(shù)千萬消費(fèi)者。隨著訂單量的增長、業(yè)務(wù)復(fù)雜度的提升,訂單系統(tǒng)也在不斷演變進(jìn)化,從早期一個(gè)訂單業(yè)務(wù)模塊到現(xiàn)在分布式可擴(kuò)展的高并發(fā)、高性能、高可用訂單系統(tǒng)。整個(gè)發(fā)展過程中,訂單系統(tǒng)經(jīng)歷了幾個(gè)明顯的階段,通過不同的技術(shù)優(yōu)化方案解決業(yè)務(wù)上遇到的問題。
下面我將為大家逐一介紹我們遇到了哪些問題及如何解決,主要分為以下三部分:
-  京東到家系統(tǒng)架構(gòu) 
-  訂單系統(tǒng)架構(gòu)演進(jìn) 
-  訂單系統(tǒng)穩(wěn)定性保障實(shí)踐 
1
?
? ?
京東到家系統(tǒng)架構(gòu)
1.1
?
? ?
業(yè)務(wù)架構(gòu)
首先來看以下這張流程圖,這個(gè)系統(tǒng)架構(gòu)主要由幾個(gè)部分構(gòu)成:用戶端分別是 C 端用戶和 B 端用戶。B 端用戶針對的是像沃爾瑪、永輝超市等的一些商家,商家生產(chǎn)需要用到我們的一些揀貨 APP 和揀貨助手,后面商家履約完成會(huì)用到配送端,配送端就是給騎手接單搶單,最后是結(jié)算部分,分別給騎手和商家結(jié)算。
?C 端針對的是用戶,用戶進(jìn)來瀏覽、下單到支付,整個(gè)過程是用戶的操作行為。基于用戶的操作行為,我們有幾大模塊來支撐,首先是京東到家 APP 的后端業(yè)務(wù)支撐的基礎(chǔ)服務(wù),另外就是營銷系統(tǒng)、業(yè)務(wù)系統(tǒng)等等。基于上面這些,我們需要有很多系統(tǒng)來支撐,比如運(yùn)營支撐系統(tǒng)、管理后臺(tái)的支撐系統(tǒng)、對商家的履約支撐系統(tǒng)。這些業(yè)務(wù)系統(tǒng)的底層大概有三塊的持久化,分別是緩存(LocalCache、Redis 等)、DB(MySQL、MongoDB 等數(shù)據(jù)庫)、ES。這就是京東到家簡版的業(yè)務(wù)架構(gòu)圖。
1.2
?
? ?
運(yùn)營支撐業(yè)務(wù)架構(gòu)
京東到家的運(yùn)營支撐業(yè)務(wù)架構(gòu)主要分為商家管理、CMS 管理、營銷管理、財(cái)務(wù)管理、運(yùn)營數(shù)據(jù)這五大模塊,每塊包含的內(nèi)容具體如下圖所示:
1.3
?
? ?
后端架構(gòu)
接下來是我們 C 端 APP 的一些網(wǎng)關(guān)后端的接口及基礎(chǔ)服務(wù)的支撐。首先所有的請求都會(huì)經(jīng)過網(wǎng)關(guān)協(xié)議,網(wǎng)關(guān)下面分為業(yè)務(wù)系統(tǒng),包括首頁、門店頁、購物車、結(jié)算頁以及提單系統(tǒng)、支付系統(tǒng)和個(gè)人訂單系統(tǒng),這些系統(tǒng)的支撐都離不開我們的基礎(chǔ)服務(wù)的支撐,比如庫存、商品、門店、價(jià)格等等,這些是一些重要的基礎(chǔ)服務(wù)支撐,來保證用戶可以流暢的下單以及到結(jié)算。
業(yè)務(wù)支撐包含了很多業(yè)務(wù)系統(tǒng),比如用戶、定位、地址庫、運(yùn)費(fèi)、promise、推薦、搜索、收銀臺(tái)、風(fēng)控等。
上面說到了營銷的一些管理系統(tǒng),其實(shí)營銷還有一些后端的基礎(chǔ)服務(wù)系統(tǒng),比如優(yōu)惠券、滿減、秒殺、首單等。
1.4
?
? ?
訂單數(shù)據(jù)入庫流程
用戶提單以后數(shù)據(jù)怎么流轉(zhuǎn)?提單其實(shí)是一個(gè)把用戶下單數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫,提單系統(tǒng)做了一些分庫分表。那么提完單的數(shù)據(jù)怎么下發(fā)到訂單系統(tǒng)生產(chǎn)?首先我們會(huì)有一個(gè)管道,提單通過一個(gè)分布式異步任務(wù)來下發(fā)訂單管道里。所有的訂單下來都會(huì)放到管道里,我們通過一個(gè)異步的任務(wù),按照一定的速率,均勻地把訂單下發(fā)到訂單生產(chǎn)系統(tǒng)。這樣設(shè)計(jì)有一個(gè)好處,比如像大促時(shí)可能會(huì)有大量數(shù)據(jù)一下子下發(fā)到訂單生產(chǎn)系統(tǒng),對訂單生產(chǎn)庫有很大壓力,所以我們中間設(shè)計(jì)出一個(gè)管道,通過異步任務(wù)來分發(fā)生產(chǎn)訂單。
看了圖可能有人會(huì)問為什么要有一個(gè)個(gè)人訂單 DB?其實(shí)個(gè)人訂單 DB 跟訂單系統(tǒng)是不同維度的數(shù)據(jù),因?yàn)閭€(gè)人訂單其實(shí)是基于用戶去做了一個(gè)分庫分表,它每一個(gè)查詢的訂單都是基于這種個(gè)人,跟訂單生產(chǎn)是不一樣的,所以每個(gè)維度的數(shù)據(jù)都是單獨(dú)的存儲(chǔ),來提高系統(tǒng)的穩(wěn)定性,以及適合它自身業(yè)務(wù)特性的設(shè)計(jì)。
那么訂單系統(tǒng)跟個(gè)人中心是怎么交互的?首先異步,我們是通過 MQ 來交互這些訂單狀態(tài)的變更。另外 C 端的訂單取消,是怎么同步到訂單生產(chǎn)系統(tǒng)的?我們是通過 RPC 的調(diào)用來保證訂單實(shí)時(shí)取消,有一個(gè)返回結(jié)果。
2
?
? ?
訂單系統(tǒng)架構(gòu)演進(jìn)
2.1
?
? ?
訂單系統(tǒng)業(yè)務(wù)流程
我們的訂單履約分為兩大塊,商家生產(chǎn)和配送履約,具體步驟如下圖所示:
整個(gè)訂單履約的流程是怎么樣的?在用戶支付完成后,訂單會(huì)有一個(gè)補(bǔ)全的過程,補(bǔ)全完后,我們會(huì)根據(jù)一些門店的設(shè)置,把訂單下發(fā)到商家。下發(fā)商家后,有幾種對接模式:有開發(fā)能力的商家可以通過開放平臺(tái),一些小商家可以通過商家中心以及我們的京明管家來完成訂單的生產(chǎn)履約。在商家拿到新訂單后,通過打印出小票進(jìn)行揀貨。揀貨會(huì)分為幾個(gè)業(yè)務(wù)場景,因?yàn)橛锌赡苌碳矣胸浺灿锌赡軟]貨,如果缺貨的話,我們有一個(gè)調(diào)整的功能,讓商家通過訂單調(diào)整來保證有商品的訂單可以繼續(xù)履約。
在商家完成揀貨時(shí),我們訂單會(huì)分為分區(qū)揀貨、合單揀貨、前置倉揀貨這幾塊業(yè)務(wù)上的操作,其實(shí)在系統(tǒng)里我們有一個(gè)揀貨的池子,會(huì)通過不同維度的數(shù)據(jù)來完成高效的揀貨。揀貨完成后,我們的配送主要分為兩個(gè)模塊,一種是單個(gè)訂單的配送,另一種是集合單的配送。集合單就是把發(fā)單地址和配送地址在兩個(gè)相近的格子里的訂單合并起來,基本上都是基于將同一個(gè)門店的配送目的是同一個(gè)相近格子里的訂單,進(jìn)行合單后讓一個(gè)騎士完成配送。配送會(huì)下發(fā)到一些配送系統(tǒng),分為兩種模式,集合單和單個(gè)訂單的配送,以及和配送系統(tǒng)的整個(gè)運(yùn)單交互的一個(gè)流程。
2.2
?
? ?
RPC 微服務(wù)化集群
說完業(yè)務(wù)后,接下來介紹一下我們應(yīng)用的一些微服務(wù)的拆分過程。先講一下微服務(wù)理論方面的知識(shí),比如為什么要拆分微服務(wù)?微服務(wù)拆分后可以解決哪些問題?這是接下來一個(gè)重點(diǎn)內(nèi)容,大家可以思考一下,系統(tǒng)架構(gòu)必須具備哪些條件才能達(dá)到高可用?
簡單總結(jié)來說,微服務(wù)可以降低系統(tǒng)的復(fù)雜度,可以獨(dú)立部署,并且有很好的擴(kuò)展性:
-  降低復(fù)雜度:把原來耦合在一起的業(yè)務(wù),按領(lǐng)域拆分為不同的微服務(wù),拆分原有的復(fù)雜邏輯,每個(gè)微服務(wù)專注于單一業(yè)務(wù)邏輯。明確定義領(lǐng)域職責(zé)與邊界; 
-  獨(dú)立部署:由于微服務(wù)具備獨(dú)立部署運(yùn)行能力,當(dāng)業(yè)務(wù)發(fā)生變更升級(jí)時(shí),微服務(wù)可以單獨(dú)開發(fā)、測試、部署升級(jí)。提高了迭代效率,降低了研發(fā)風(fēng)險(xiǎn); 
-  擴(kuò)展性:拆分后的微服務(wù)架構(gòu)獨(dú)立部署,可以根據(jù)流量預(yù)估或壓測評估獨(dú)立進(jìn)行擴(kuò)容升級(jí)。 
我們訂單系統(tǒng)的架構(gòu)演進(jìn)如上圖所示,最左邊是最初的一個(gè)模型,所有的業(yè)務(wù)都耦合在一個(gè)應(yīng)用里面,這個(gè)應(yīng)用可能就有一個(gè) service 來支撐,數(shù)據(jù)庫也是一個(gè)單點(diǎn)的數(shù)據(jù)庫。隨著這些年的迭代升級(jí)變更,逐步演進(jìn)到一個(gè)應(yīng)用有多個(gè)服務(wù)支撐,數(shù)據(jù)庫也在不斷升級(jí)變更,以及到后面把應(yīng)用按微服務(wù)拆分成多個(gè)模塊,拆成多個(gè)領(lǐng)域的支撐,按不同的系統(tǒng)邊界去拆分。并且拆完后,隨著業(yè)務(wù)量越來越大,其實(shí)我們也在做一些升級(jí),比如 Redis 的接入。
Redis 的接入解決了什么問題?數(shù)據(jù)庫為什么要分庫?ES 為什么在接下來一些系統(tǒng)架構(gòu)升級(jí)里會(huì)被引入進(jìn)來?為什么 DB 要拆成多個(gè)集群?這背后的一些根本問題,以及解決業(yè)務(wù)系統(tǒng)的一些背景,接下來我們逐一探討。
在最初搭建項(xiàng)目時(shí),其實(shí)我們是要保證業(yè)務(wù)的快速試錯(cuò)。這個(gè)模型會(huì)有什么問題?就是系統(tǒng)會(huì)有一些單點(diǎn)風(fēng)險(xiǎn),以及系統(tǒng)發(fā)布是一個(gè)短暫停,所有請求都是一個(gè)主觀的操作,并發(fā)能力很差,稍微有一些業(yè)務(wù)量時(shí),系統(tǒng)接口就會(huì)超時(shí)比較嚴(yán)重。這是最初 2015-2016 年的情況。
接下來 2016-2017 年,隨著業(yè)務(wù)的快速迭代,系統(tǒng)復(fù)雜度也慢慢高了起來,系統(tǒng)邏輯耦合會(huì)比較嚴(yán)重,改動(dòng)一塊的邏輯影響就會(huì)比較大,導(dǎo)致線上問題頻發(fā),因?yàn)樗械倪壿嫸捡詈显谝黄?#xff0c;一次發(fā)布可能就會(huì)影響范圍比較大。
按微服務(wù)拆分成多個(gè)系統(tǒng),如果發(fā)布有問題也只會(huì)影響其中的一些很小的部分。在后面隨著業(yè)務(wù)量越來越大,RPC 這種框架的引入,解決故障的自動(dòng)下線,保證高可用,比如單臺(tái)服務(wù)器有問題時(shí),能做到自動(dòng)下線來保證不影響業(yè)務(wù)。
2.3
?
? ?
訂單系統(tǒng)架構(gòu)升級(jí) - Redis 集群
2017-2018 年,我們根據(jù) 2016 年遇到的問題做了一些拆分,比如按領(lǐng)域拆分不同的 APP 應(yīng)用。這樣拆分做到的就是系統(tǒng)沒有單點(diǎn),負(fù)載均衡可以橫向擴(kuò)展,多點(diǎn)部署。包括引入 Redis,其實(shí)我們用到了 Redis 的分布式鎖、緩存、有序隊(duì)列、定時(shí)任務(wù)。
我們數(shù)據(jù)庫為什么升級(jí)?因?yàn)閿?shù)據(jù)庫的數(shù)據(jù)量越來越大,比如添加一些字段,它其實(shí)會(huì)做一些鎖表操作,隨著數(shù)據(jù)量越大,單表的數(shù)據(jù)越來越多,數(shù)據(jù)主從延遲以及一些鎖表的時(shí)間會(huì)越來越長,所以在加字段的時(shí)候?qū)ιa(chǎn)影響特別大,我們就會(huì)對數(shù)據(jù)做一個(gè)分離,把一些冷的數(shù)據(jù)單獨(dú)做一個(gè)歷史庫,剩下的生產(chǎn)庫只留最近幾天的一些生產(chǎn)需要的數(shù)據(jù),這樣生產(chǎn)庫的訂單數(shù)據(jù)量就會(huì)很小,每次修改表的時(shí)間是可控的,所以我們會(huì)把數(shù)據(jù)按照冷備進(jìn)行拆分。
至于為什么引入 ES,是因?yàn)橛唵卧谏a(chǎn)方面會(huì)有一些很復(fù)雜的查詢,復(fù)雜查詢對數(shù)據(jù)庫的性能影響非常大,引入 ES 就可以很好地解決這個(gè)問題。
2018-2019 年,我們發(fā)現(xiàn)之前在引入數(shù)據(jù)庫時(shí),用數(shù)據(jù)冗余來保證一些數(shù)據(jù)應(yīng)用可互備互降。比如我們之前在用 ES 低版本 1.7 的時(shí)候,其實(shí)就是一個(gè)單點(diǎn),當(dāng)集群有問題時(shí)是會(huì)影響生產(chǎn)的。我們后來引入了一個(gè)雙集群,雙 ES 集群互備,當(dāng)一個(gè)集群有問題時(shí),另一個(gè)集群可以直接頂上來,確保了應(yīng)用的高可用和生產(chǎn)沒有問題。
另外,引入 Redis 雙集群,Redis 其實(shí)有一些大 key 的問題,當(dāng)一些核心業(yè)務(wù)和非核心業(yè)務(wù)都用到 Redis 的時(shí)候,我們會(huì)把一些核心業(yè)務(wù)拆到一個(gè)集群,非核心業(yè)務(wù)拆到另一個(gè)集群,來保證 Redis 集群的穩(wěn)定性,能穩(wěn)定支撐訂單生產(chǎn)。
注:大 key(線上看到過 list 的 elements 超過百萬的)刪除時(shí)會(huì)阻塞比較長的時(shí)間,大 key 的危害:
OPS 低也會(huì)導(dǎo)致流量大,比如一次取走 100K 的數(shù)據(jù),當(dāng) OPS 為 1000 時(shí),就會(huì)產(chǎn)生 100M/s 的流量;
如果為 list,hash 等數(shù)據(jù)結(jié)構(gòu),大量的 elements 需要多次遍歷,多次系統(tǒng)調(diào)用拷貝數(shù)據(jù)消耗時(shí)間;
主動(dòng)刪除、被動(dòng)過期刪除、數(shù)據(jù)遷移等,由于處理這一個(gè) key 時(shí)間長,而發(fā)生阻塞。
單個(gè)應(yīng)用怎么保證高可用?其實(shí)我們這邊從最初的一個(gè)單機(jī)房單實(shí)例單 IP,一步步演進(jìn)為單機(jī)房的單服務(wù)、單機(jī)房的多服務(wù),最終是多機(jī)房的多服務(wù),比如某個(gè)機(jī)房某些 IP 有問題,我們能確保應(yīng)用可以正常請求響應(yīng),來保證系統(tǒng)的高可用。
2.4
?
? ?
訂單系統(tǒng)數(shù)據(jù)架構(gòu) - MySQL
下面來介紹一下我們主從架構(gòu)的演進(jìn)。最初我們就是一些主從的架構(gòu),并且主讀和寫都會(huì)請求到一個(gè)主庫,這樣就會(huì)給主庫帶來非常大的壓力。而像訂單生產(chǎn)這種天然性就是讀多寫少,主庫的壓力會(huì)比較大。所以基于這個(gè)問題,我們就做了數(shù)據(jù)庫架構(gòu)的升級(jí)。
我們做了讀寫分離,就能很好地解決了這個(gè)問題。比如很多查詢,我們就會(huì)直接查詢到從庫的,主庫只是用來承載一些寫的流量,這樣主庫就減少了很多壓力。但這樣就會(huì)遇到上面說到的問題,因?yàn)槲覀兪且粋€(gè)生產(chǎn)表和歷史表的結(jié)構(gòu),在每次加字段時(shí),數(shù)據(jù)量很多的話,鎖表時(shí)間就會(huì)很長,導(dǎo)致在這種讀寫分離的架構(gòu)下數(shù)據(jù)延遲就會(huì)比較大。
基于這種場景,我們又做了一些升級(jí)和改造。我們把數(shù)據(jù)拆出來了,拆成歷史庫,當(dāng)所有的生產(chǎn)數(shù)據(jù)都很小的時(shí)候,對于提高性能是非常有幫助的。我們把生產(chǎn)完的一些數(shù)據(jù)全部都?xì)w檔到歷史中,減輕主庫的整個(gè)壓力,以及在添加表字段修改的時(shí)候,其實(shí)就沒有太大影響了,基本上都很穩(wěn)定。
數(shù)據(jù)的演進(jìn)最終結(jié)構(gòu)如上圖,當(dāng)這是基于目前業(yè)務(wù)的一個(gè)支撐,在未來業(yè)務(wù)不斷發(fā)展的情況下,這個(gè)數(shù)據(jù)庫架構(gòu)是原因不夠的。基于以上架構(gòu),我們主要是做到了一主多從的主備實(shí)時(shí)切換,同時(shí)確保主從在不同機(jī)房來保證數(shù)據(jù)庫的容災(zāi)能力。同時(shí)通過業(yè)務(wù)隔離查詢不同的從庫給主庫減輕壓力,以及冷備數(shù)據(jù)的隔離的一個(gè)思路來保證訂單數(shù)據(jù)庫的穩(wěn)定性。
2.5
?
? ?
訂單系統(tǒng)架構(gòu)升級(jí) - ES 集群
最開始我們是單 ES 集群,DB 會(huì)通過一個(gè)同步寫寫到 ES 集群。這個(gè)時(shí)候我們的 ES 是一個(gè)單機(jī)群,如果寫失敗的話,我們會(huì)起一個(gè)異步任務(wù)來保證數(shù)據(jù)的最終一致性,是這樣的一個(gè)架構(gòu)。在 ES 集群沒問題的情況下,這個(gè)架構(gòu)也是沒問題的,但當(dāng)集群有問題時(shí),其實(shí)就沒有可降級(jí)的了。
為了解決這個(gè)問題,我們引入了 ES 的冷備兩個(gè)集群,熱集群只保存跟數(shù)據(jù)庫一樣的生產(chǎn)庫的數(shù)據(jù),比如說我們現(xiàn)在保證的就是 5 天的生產(chǎn)數(shù)據(jù),其它所有數(shù)據(jù)都?xì)w檔在一個(gè) ES 的冷集群里。通過這種異步跟同步寫,通過異步任務(wù)來保證最終的集群的數(shù)據(jù)一致性。這就是 ES 的架構(gòu)升級(jí)。
其實(shí) ES 這樣寫的話會(huì)帶來一些很大的侵入,每次我們數(shù)據(jù)庫的一個(gè)變更都會(huì)帶來一些要 RPC 調(diào)用去同步 ES 的數(shù)據(jù)。這種瓶頸以及侵入式的問題怎么解決?我們接入了開源的 Canal,通過監(jiān)聽數(shù)據(jù)庫變更了 binlog,來通過 Canal、kafka,然后異步通過消息來同步到 ES 集群。這個(gè)集群目前已經(jīng)應(yīng)用到線上的一些業(yè)務(wù),經(jīng)過灰度發(fā)布、后期驗(yàn)證沒有問題后,會(huì)逐步接入生產(chǎn)系統(tǒng)。
如上圖所示,是我們整個(gè)訂單系統(tǒng)的結(jié)構(gòu)。整個(gè)過程我們是通過業(yè)務(wù)網(wǎng)關(guān)、RPC 高可用、業(yè)務(wù)聚合、DB 冗余、多機(jī)房部署,來保證整個(gè)訂單應(yīng)用的一些系統(tǒng)架構(gòu)高可用。上述就是整體的訂單架構(gòu)演進(jìn)過程。
3
?
? ?
訂單系統(tǒng)穩(wěn)定性保障實(shí)踐
大家思考一下,如果你要負(fù)責(zé)一些核心系統(tǒng),你怎么保證穩(wěn)定性?接下來我會(huì)從訂單系統(tǒng)可用性建設(shè)、系統(tǒng)容災(zāi)能力、系統(tǒng)容量能力、系統(tǒng)預(yù)警能力分享一下我們在穩(wěn)定性保障上的實(shí)踐。
3.1
?
? ?
訂單系統(tǒng)可用性建設(shè)
業(yè)務(wù)的快速發(fā)展對可用性保證要求越來越高,在方法論層面,我們按照系統(tǒng)故障的時(shí)間順序提出了事前、事中、事后三個(gè)階段,同時(shí)提出了四方面的能力建設(shè)——預(yù)防能力、診斷能力、解決能力、規(guī)避能力。
具體在工作上,我們會(huì)劃分為流程和系統(tǒng)建設(shè)兩個(gè)方面。其實(shí)最開始我們是為了完成工作,保證的是結(jié)果,最后發(fā)現(xiàn)每一個(gè)過程我們會(huì)把它平臺(tái)化,來提升人效。以上是一個(gè)大概的框架,下面我們一項(xiàng)一項(xiàng)詳細(xì)分析一下。
3.2
?
? ?
系統(tǒng)容災(zāi)能力
容災(zāi)能力這塊,我們從 ES 冷熱集群互備、Redis 緩存集群業(yè)務(wù)隔離,到業(yè)務(wù)接口的可降級(jí)和可異步,再到多維度的灰度發(fā)布。
就像上面提到的,我們對開放平臺(tái)、商家中心、京明管家等業(yè)務(wù)系統(tǒng)的支撐怎么做到互備?其實(shí)就是通過 ES 的冷熱集群,冷集群存全量的數(shù)據(jù),熱集群存最近幾天的生產(chǎn)數(shù)據(jù)。而 Redis 是做業(yè)務(wù)隔離,Redis 存儲(chǔ)有一些大 key 會(huì)影響核心業(yè)務(wù),我們就會(huì)把非核心的業(yè)務(wù)拆出來,拆到另外一個(gè) Redis 集群。這就是我們系統(tǒng)的業(yè)務(wù)隔離和集群的互備。
業(yè)務(wù)接口可降級(jí),其實(shí)是在一些復(fù)雜的業(yè)務(wù)操作接口里,我們可以通過一些異步處理,比如在訂單狀態(tài)變更的操作接口、除了更新 DB 和 ES、發(fā)送 MQ,訂單狀態(tài)的變更通過消息去同步發(fā)送。那么我們哪些可降低?比如說我們在業(yè)務(wù)核心操作接口里有一些 push 消息和發(fā)送短信,像這樣的非核心操作就可以用異步可降級(jí)的方案來解決。
灰度發(fā)布其實(shí)很重要,線上如果有一些新業(yè)務(wù)或系統(tǒng)的架構(gòu)升級(jí)、技術(shù)的迭代,我們這邊都會(huì)通過灰度發(fā)布,比如可以通過一些門店先做門店匯總,如果單個(gè)門店沒問題,再通過一些商家,如果商家沒問題,就會(huì)灰度到整個(gè)城市,如果城市也沒問題,我們就會(huì)通過全量。
另一個(gè)維度來看,我們也會(huì)先灰度單臺(tái)機(jī)器,再到單機(jī)房、多機(jī)房。當(dāng)然這個(gè)很局限,因?yàn)楦慊叶鹊囊恍┕δ苡嘘P(guān)系,大家要酌情根據(jù)自己的業(yè)務(wù)借鑒這種方案思路。
3.3
?
? ?
系統(tǒng)容量能力
至于系統(tǒng)容量的能力,主要分為評估和擴(kuò)容兩個(gè)方面。容量評估可以借助一些輔助的工具,然后進(jìn)行場景的壓測和全鏈路的壓測;而擴(kuò)容方面,可以分階段依次實(shí)施冗余備份(主從分離)、垂直拆分(拆分核心屬性與非核心屬性)、自動(dòng)歸檔。
3.4
?
? ?
系統(tǒng)預(yù)警能力
最后是預(yù)警能力,我們這邊用的是京東自研的 UMP 監(jiān)控。
在接口層面,我們可以監(jiān)控到:
-  可用率; 
-  響應(yīng)時(shí)間; 
-  調(diào)用量:當(dāng)別人調(diào)用你的接口,你設(shè)置調(diào)用的一個(gè)量值,當(dāng)超過閥值時(shí)就是進(jìn)來了一些非正常的流量,當(dāng)監(jiān)控到這些異常流量,就可以做限流等相應(yīng)操作; 
-  自定義:自定義一些報(bào)警; 
-  關(guān)鍵詞:當(dāng)系統(tǒng)出現(xiàn)某種問題,需要關(guān)鍵字報(bào)出來然后進(jìn)行人工介入; 
-  調(diào)用鏈:一個(gè)接口操作下來,誰調(diào)用了你?你調(diào)用了誰?哪個(gè)環(huán)節(jié)有問題? 
在應(yīng)用層面,我們可以監(jiān)控到:
-  Young GC; 
-  Full GC; 
-  堆內(nèi)存; 
-  非堆內(nèi)存; 
-  CPU 使用率; 
-  線程數(shù)。 
下面是關(guān)于 DB、ES、Redis 的集群監(jiān)控,包括:
-  CPU 使用率; 
-  系統(tǒng)負(fù)載; 
-  內(nèi)存; 
-  線程數(shù); 
-  讀寫 IO; 
-  磁盤使用率; 
-  TCP 連接數(shù)。 
?
如果大家有排查過線上的問題,就應(yīng)該能感受到比如像 IO 高、TCP 連接、重傳等,都會(huì)影響到線上一些核心接口的響應(yīng)時(shí)間,包括你 CPU 高的時(shí)候,線程數(shù)是否飆高?系統(tǒng)負(fù)載是否飆高?當(dāng)這些指標(biāo)都發(fā)生異常變化時(shí),對于接口的響應(yīng)時(shí)間都會(huì)有很大影響。
另外,我們還要做一些積壓監(jiān)控,比如一些異步任務(wù)正常來說一分鐘最多積壓 1000,就需要添加對應(yīng)的監(jiān)控,否則數(shù)據(jù)異常了都不知道;再比如訂單支付的狀態(tài),當(dāng)積壓到一定數(shù)量,可能是系統(tǒng)出了問題,就需要人工介入。
4
?
? ?
總結(jié)
一個(gè)企業(yè)的架構(gòu)師團(tuán)隊(duì),需要長期關(guān)注技術(shù)驅(qū)動(dòng)業(yè)務(wù)、明確領(lǐng)域職責(zé)與邊界等關(guān)鍵問題,同時(shí)架構(gòu)的演進(jìn)過程也是不斷考慮 ROI 的權(quán)衡取舍過程。技術(shù)的持續(xù)發(fā)展,有助于不斷提升用戶體驗(yàn)、業(yè)務(wù)規(guī)模,降低運(yùn)營成本,而架構(gòu)在其中需要解決的問題就是化繁為簡,將復(fù)雜問題拆解為簡單的問題逐個(gè)攻破。隨著企業(yè)規(guī)模的持續(xù)增長、業(yè)務(wù)的持續(xù)創(chuàng)新,會(huì)給系統(tǒng)架構(gòu)提出越來越高的要求,所以系統(tǒng)架構(gòu)設(shè)計(jì)將是我們長期研究的課題。在這條架構(gòu)演進(jìn)之路上,希望能與大家共勉共進(jìn)。
?
>>>>
Q&A
?
Q1:集群規(guī)模大概是什么樣的?各集群節(jié)點(diǎn)規(guī)模如何?
?
A:京東到家訂單中心ES 集群目前大約有將近30億文檔數(shù),數(shù)據(jù)大小約1.3TB,集群結(jié)構(gòu)是8個(gè)主分片,每個(gè)主分片有兩個(gè)副本,共24個(gè)分片。每個(gè)機(jī)器上分布1-2個(gè)分片,如果企業(yè)不差錢最好的狀態(tài)就是每個(gè)分片獨(dú)占一臺(tái)機(jī)器。這些集群規(guī)模和架構(gòu)設(shè)計(jì)不應(yīng)該是固定的,每一個(gè)業(yè)務(wù)系統(tǒng)應(yīng)該根據(jù)自身實(shí)際業(yè)務(wù)去規(guī)劃設(shè)計(jì)。
?
這樣做確定分片數(shù):
?
-  ES是靜態(tài)分片,一旦分片數(shù)在創(chuàng)建索引時(shí)確定那么后繼不能修改; 
-  數(shù)據(jù)量在億級(jí)別,8或者16分片夠用,分片數(shù)最好是2的n次方; 
-  如果后繼數(shù)據(jù)量的增長超過創(chuàng)建索引的預(yù)期,那么需要?jiǎng)?chuàng)建新索引并重灌數(shù)據(jù); 
-  創(chuàng)建mapping是請自行制定分片數(shù),否則創(chuàng)建的索引的分片數(shù)是ES的默認(rèn)值。這其實(shí)并不符合需求; 
-  副本數(shù):一般設(shè)置為1,特色要求除外。 
?
Q2:ES 優(yōu)化做過哪些措施?
?
A:ES使用最佳實(shí)踐:寫入的數(shù)據(jù)考慮清楚是否會(huì)過期,ES擅長的不是存儲(chǔ)而是搜索,所以一般存入ES的數(shù)據(jù)難免會(huì)隨著時(shí)間刪除舊數(shù)據(jù)。刪除方法有兩種:①按記錄(不推薦)②按索引。推薦使用后者,所以需要業(yè)務(wù)根據(jù)數(shù)據(jù)特點(diǎn),按天、月、季度等創(chuàng)建索引。分片數(shù)夠用就好,不要過多不要過少。ES不是數(shù)據(jù)庫,不建議做頻繁的更新。
?
Q3:集群可承受的 TPS 是多少?
?
A:這個(gè)沒有具體的數(shù)字,根據(jù)不同規(guī)模集群、不同的索引結(jié)構(gòu)等不同,建議根據(jù)業(yè)務(wù)評估自己的流量,壓測雙倍流量,若ES無法承受或滿足,可以考慮擴(kuò)容集群,不要流量暴增于平時(shí)的3倍、4倍,甚至更多的規(guī)模。
?
Q4:ES 主要是用于明細(xì)單查詢,還是聚合統(tǒng)計(jì)?Join 對資源耗用大嗎?如何控制內(nèi)存及優(yōu)化?
?
A:ES在訂單系統(tǒng)中的實(shí)踐主要是解決復(fù)雜查詢的問題,ES不建議使用聚合統(tǒng)計(jì),如果非要使用那我也攔不住你,哈哈哈。
?
深分頁的問題【內(nèi)存】
ES處理查詢的流程如下:
Client需要第N到N+m條結(jié)果;
接到這個(gè)請求的ES server(后繼稱之為協(xié)調(diào)者)向每一個(gè)數(shù)據(jù)分片所在的數(shù)據(jù)節(jié)點(diǎn)發(fā)送請求;
每一個(gè)數(shù)據(jù)節(jié)點(diǎn)都需要向協(xié)調(diào)者返回(N+m)個(gè)結(jié)果;
如果有n個(gè)數(shù)據(jù)分片,那么協(xié)調(diào)者拿到n * (N+m)個(gè)結(jié)果,排序,扔掉(n-1) * (N+m)個(gè)結(jié)果,返回給client N+m個(gè)結(jié)果;
如果N是10W,100W,那么協(xié)調(diào)者的內(nèi)存壓力會(huì)非常大;
在我們目前維護(hù)的2.1版本中,ES已經(jīng)不容許N>10000了。
?
深分頁的危害:導(dǎo)致打爆節(jié)點(diǎn)內(nèi)存引起集群整體不可用。
?
Q5:應(yīng)用 canal 同步數(shù)據(jù),會(huì)有延遲嗎?
?
A:首先來說下ES 特點(diǎn):Elasticsearch是一個(gè)接近實(shí)時(shí)的搜索平臺(tái)。這意味著,從索引一個(gè)文檔直到這個(gè)文檔能夠被搜索到有一個(gè)輕微的延遲(通常是1秒),這個(gè)參數(shù)也是可以調(diào)整的,根據(jù)業(yè)務(wù)場景調(diào)整。
?
可以肯定的是延遲是肯定的。其實(shí)延遲大小完全取決你整個(gè)同步流程中是否有瓶頸點(diǎn),如果業(yè)務(wù)要求實(shí)時(shí)的,其實(shí)不建議在這種場景下使用ES。就好比數(shù)據(jù)庫查詢從庫不能接受延遲,那就不要做讀寫分離,或者都查詢主庫。
?
Q6:sqlproxy 具體用的哪個(gè)?
?
A:sqlproxy這個(gè)是指MySQL讀寫分離的實(shí)現(xiàn),大家可以網(wǎng)上查詢下有很多資料。官網(wǎng)地址:https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-master-slave-replication-connection.html
?
?
<mvn.jdbc.driver>com.mysql.jdbc.ReplicationDriver</mvn.jdbc.driver>
<mvn.jdbc.url>jdbc:mysql:replication://m.xxx.com:3306,s.xxx.com:3306/dbName</mvn.jdbc.url>
?
?
Q7:Redis 用于查詢緩存、分發(fā)任務(wù)緩存?
?
A:Redis在項(xiàng)目中的使用場景,緩存查詢,分布式鎖使用。其中還有一個(gè)異步任務(wù)是通過redis zset + tbschedule 定時(shí)或?qū)崟r(shí)的去執(zhí)行一些業(yè)務(wù)邏輯。
?
添加隊(duì)列數(shù)據(jù)方法:
?
public Boolean zAdd(String key, final double score, String value) ;
?
查詢獲取隊(duì)列數(shù)據(jù):
?
public Set<String> zRangeByScore(String key, final double min, final double max) ;
?
Q8:容量評估可以講一些細(xì)節(jié)嘛?
?
A:基本有兩種場景:
?
日常業(yè)務(wù)流程是否有瓶頸 ;
大促期間根據(jù)流量預(yù)估系統(tǒng)是否有瓶頸。
?
京東到家內(nèi)部系統(tǒng)是有一套完整的監(jiān)控系統(tǒng),基于接口,應(yīng)用機(jī)器,集群的多維度監(jiān)控。
?
-  接口: 
-  響應(yīng)時(shí)間,tp99,tp999,tp9999 等; 
-  接口調(diào)用量,次數(shù)/分鐘; 
-  可用率。 
?
-  應(yīng)用機(jī)器 
根據(jù)監(jiān)控可以查看單機(jī)器相關(guān)指標(biāo)數(shù)據(jù)是否正常,比如:
-  CPU使用率; 
-  系統(tǒng)負(fù)載; 
-  網(wǎng)絡(luò)IO; 
-  TCP連接數(shù),線程數(shù); 
-  磁盤使用率。 ? 
-  集群 
-  Redis集群; 
-  ES集群; 
-  MySQL集群。 
?
對于集群來說是根據(jù)集群下機(jī)器指標(biāo)是否正常來評估整個(gè)集群是否正常。需要看集群可以承載業(yè)務(wù)流量的TPS、QPS等指標(biāo)是否滿足業(yè)務(wù)需求,同時(shí)需要評估大促場景下是否可以滿足要求。這種情況就需要根據(jù)大促流量評估壓測,看集群以及應(yīng)用,接口是否可以滿足需求。
?
每個(gè)公司可以根據(jù)自身規(guī)則進(jìn)行擴(kuò)容,及架構(gòu)升級(jí)。比如日常CPU超過60%考慮應(yīng)用擴(kuò)容,負(fù)載遠(yuǎn)大于機(jī)器核數(shù)等等。
?
Q9:異步定時(shí)任務(wù)用的是什么中間件?
?
A:tbschedule是一個(gè)支持分布式的調(diào)度框架,讓批量任務(wù)或者不斷變化的任務(wù)能夠被動(dòng)態(tài)的分配到多個(gè)主機(jī)的JVM中, 在不同的線程組中并行執(zhí)行,所有的任務(wù)能夠被不重復(fù),不遺漏的快速處理。基于ZooKeeper的純Java實(shí)現(xiàn),由Alibaba開源。
?
Q10:在云上部署還是物理服務(wù)器?
?
A:應(yīng)用都部署在云服務(wù)器上,首先即時(shí),幾分鐘即可完成,可一鍵部署、也可自主安裝操作系統(tǒng)。安全性方面因?yàn)榉?wù)分布在多臺(tái)服務(wù)器、甚至多個(gè)機(jī)房,所以不容易徹底宕機(jī),抗災(zāi)容錯(cuò)能力強(qiáng),可以保證長時(shí)間在線。彈性以及可擴(kuò)展性方面云主機(jī)基本特點(diǎn)就是分布式架構(gòu),所以可以輕而易舉地增加服務(wù)器,成倍擴(kuò)展服務(wù)能力。
?
Q11:RPC 高可用怎么實(shí)現(xiàn)?
?
A:RPC高可用基本都是借助于分布式框架,阿里開源dubbo,Spring全家桶的SpringCloud,包括我們使用的京東自研的JSF。其工作原理,感興趣的同學(xué)可以網(wǎng)上搜下,很多資料。在這兒就不一一解答了。
總結(jié)
以上是生活随笔為你收集整理的京东订单系统高可用架构及演变过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 编译PBRT-v3源码
- 下一篇: Object-C 函数参数语法
