京東服務(wù)市場(chǎng)高并發(fā)下SOA服務(wù)化演進(jìn)架構(gòu)
京東服務(wù)市場(chǎng)是京東商家與第三方獨(dú)立軟件提供商(ISV)進(jìn)行服務(wù)類的在線交易平臺(tái)。作為京東生態(tài)圈重要的一環(huán),伴隨著整個(gè)京東的快速增長(zhǎng),也在快速的發(fā)展。隨著服務(wù)市場(chǎng)訪問(wèn)、交易量指數(shù)級(jí)的增長(zhǎng),系統(tǒng)由原來(lái)的ALL IN ONE架構(gòu),快速的演進(jìn)成為SOA架構(gòu)。
木桶的容量由木桶最短的木板決定,高并發(fā)環(huán)境下,單個(gè)服務(wù)的性能決定了整個(gè)服務(wù)市場(chǎng)的性能。 “可用插件列表服務(wù)”是服務(wù)市場(chǎng)的核心服務(wù)之一,優(yōu)化該服務(wù)性能的過(guò)程,帶動(dòng)整個(gè)服務(wù)市場(chǎng)服務(wù)架構(gòu)的演進(jìn)。
宏觀的看,大到系統(tǒng)小到模塊都由自身+外部依賴組成,性能優(yōu)化主要從自身與外部依賴兩個(gè)方面來(lái)進(jìn)行。
一、優(yōu)化自身
單線程到多線程的升級(jí),嘗試通過(guò)并行提高服務(wù)性能。
根據(jù)日志分析,整體調(diào)用中“服務(wù)詳細(xì)信息”占用時(shí)間最多,并行雖然壓縮了一些可并行服務(wù)的調(diào)用時(shí)間,但對(duì)于無(wú)法并行的“服務(wù)詳細(xì)信息”環(huán)節(jié),依然沒(méi)有改善。要改善必須找到“商品服務(wù)”性能不高的原因。
可見(jiàn)自身優(yōu)化能起一些作用,但外部依賴起著更決定性的作用。
二、解決外部依賴沖突
“商品服務(wù)”性能不高,這是為什么呢?先從“商品服務(wù)”的依賴開(kāi)始分析。單獨(dú)調(diào)用該服務(wù),或壓測(cè)該服務(wù),性能都不差,但為何線上性能卻不佳?
1. 不同服務(wù)外部依賴資源沖突
對(duì)“商品服務(wù)”依賴的資源進(jìn)行梳理,發(fā)現(xiàn)“商品服務(wù)”與“類目服務(wù)”使用相同數(shù)據(jù)庫(kù)資源,非調(diào)用高峰期資源足夠不相互影響,大并發(fā)環(huán)境下兩個(gè)服務(wù)開(kāi)始爭(zhēng)奪資源。
將依賴資源分開(kāi),不同的服務(wù)使用不同的資源,通過(guò)調(diào)用不同的數(shù)據(jù)源解決沖突。
2. 相同服務(wù)外部資源依賴沖突
解決了兩個(gè)服務(wù)對(duì)數(shù)據(jù)庫(kù)資源的依賴沖突,性能有所提高,但性能總有很大的波動(dòng),排除其他服務(wù)外部資源的依賴沖突,看看“商品服務(wù)”自身對(duì)資源是如何使用的。
“商品服務(wù)”所有功能都單一的依賴數(shù)據(jù)庫(kù)資源。服務(wù)上線后,自身多個(gè)功能開(kāi)始爭(zhēng)搶數(shù)據(jù)庫(kù)資源。
按使用場(chǎng)景進(jìn)行外部依賴資源解耦:
- 為保證交易一致性,繼續(xù)采用MySQL。MySQL的 INNODB引擎長(zhǎng)于 OLTP 在線事務(wù)處理,為了保證數(shù)據(jù)強(qiáng)一致性的場(chǎng)景繼續(xù)選擇使用MySQL數(shù)據(jù)庫(kù)。
 - 客戶端登錄用戶需要獲得***的數(shù)據(jù)反饋,且有PIN這個(gè)固定的維度。查詢條件簡(jiǎn)單,能符合KEY-VALUE方式,Redis很適合這個(gè)場(chǎng)景。
 - 大前端非登錄狀態(tài)下,訪問(wèn)的用戶無(wú)須登錄,有很大的訪問(wèn)量,更多的是獲取服務(wù)的一些介紹。大數(shù)據(jù)量,可容忍一定程度的延遲,所以采用ES來(lái)進(jìn)行查詢支撐。
 - 外部系統(tǒng)希望獲得***服務(wù)的變化,推的方式遠(yuǎn)強(qiáng)于輪訓(xùn)拉取的方式。通過(guò)MQ訂閱服務(wù)的變化情況。
 - 有復(fù)雜計(jì)算,但對(duì)實(shí)時(shí)性要求不高,服務(wù)統(tǒng)計(jì)分析系統(tǒng)通過(guò)大數(shù)據(jù)平臺(tái)獲取數(shù)據(jù)進(jìn)行分析。
 
三、建立統(tǒng)一的內(nèi)存緩存模型
計(jì)算機(jī)的世界里沒(méi)有魔法,時(shí)間換空間、空間換時(shí)間是所有方案的基礎(chǔ)。
參考常用的MySQL INNODB引擎,為加快查詢速度會(huì)在內(nèi)存中設(shè)置一塊內(nèi)存作為緩沖區(qū),將查詢結(jié)果從硬盤(pán)中加載到緩沖區(qū),下次相同的查詢直接使用緩沖區(qū)數(shù)據(jù)。同樣的,如果要提高查詢響應(yīng)速度,必須把服務(wù)數(shù)據(jù)緩存到內(nèi)存中。單機(jī)內(nèi)存有限,無(wú)法容納所有數(shù)據(jù),且服務(wù)器重啟時(shí)整個(gè)內(nèi)存重建所耗費(fèi)的時(shí)間也是無(wú)法接受的,于是選擇用Redis與ES按照不同的使用場(chǎng)景來(lái)構(gòu)造內(nèi)存緩存。
1. 選擇主動(dòng)緩存
常規(guī)的緩存方案:查詢構(gòu)建+定期失效。對(duì)有大量重復(fù)查詢的環(huán)境效果很好,但在實(shí)際情況下,在某些場(chǎng)景卻無(wú)法發(fā)揮預(yù)想中的作用。
場(chǎng)景特征:
- 每個(gè)用戶只會(huì)打開(kāi)一次客戶端,獲取一次插件信息,不會(huì)重復(fù)頻繁的去拉取列表。
 - 訪問(wèn)集中在8點(diǎn)到9點(diǎn)這個(gè)時(shí)間段。
 - 使用被動(dòng)緩存的后果:
 - 8點(diǎn)前Redis緩存內(nèi)是空的。
 - 8點(diǎn)到9點(diǎn),所有的列表信息都是***次獲取,查詢?nèi)看┩妇彺嬷苯哟虻綌?shù)據(jù)庫(kù)。
 - 8點(diǎn)到9點(diǎn)之間獲取插件列表后做了插件的續(xù)訂或權(quán)限變更,由于緩存定時(shí)失效,導(dǎo)致更新無(wú)法反饋,用戶不斷刷新插件列表直到緩存失效獲取到更新結(jié)果。人為制造流量洪峰,Redis抗住的也是這些無(wú)用的人為重復(fù)調(diào)用量。
 - 9點(diǎn)以后緩存逐漸過(guò)期,不再被使用。
 
一個(gè)測(cè)試性能很好,實(shí)際卻沒(méi)有用的緩存。
基于以上,緩存層決定通過(guò)主動(dòng)構(gòu)建的方式建立緩存。在數(shù)據(jù)修改后,將變化數(shù)據(jù)主動(dòng)的加載到Redis緩存中,緩存不再設(shè)置過(guò)期時(shí)間。
有的服務(wù)每次獲取結(jié)果都要通過(guò)非常繁瑣的計(jì)算,如果這些繁瑣的計(jì)算集中在同一時(shí)間點(diǎn),對(duì)于后端資源(數(shù)據(jù)庫(kù))是非常大的負(fù)擔(dān)。
錯(cuò)峰使用資源,把構(gòu)建緩存的過(guò)程分散在離散的調(diào)用中,集中使用時(shí)直接調(diào)用緩存獲取最終結(jié)果。
上面提到過(guò)“類目服務(wù)”獲取類目層級(jí)列表需要多次查詢數(shù)據(jù)庫(kù),這對(duì)數(shù)據(jù)庫(kù)是很大的負(fù)擔(dān)。
提前構(gòu)建,在類目創(chuàng)建或類目變更時(shí)就重新構(gòu)建類目層級(jí)列表,將結(jié)果存入緩存,高峰期使用時(shí)直接獲取已構(gòu)建完成的類目層級(jí)列表。
2. 緩存碎片化
系統(tǒng)使用一段時(shí)間后,由于業(yè)務(wù)系統(tǒng)對(duì)服務(wù)數(shù)據(jù)需求的不一致,服務(wù)開(kāi)發(fā)人員開(kāi)始為每個(gè)外部系統(tǒng)提供一塊主動(dòng)緩存。這些緩存完全不具備通用性但又?jǐn)?shù)量眾多。每次服務(wù)模型修改,研發(fā)人員都要花大量時(shí)間去維護(hù)這些不通用的緩存。占用的緩存越來(lái)越多,但緩存的使用率并不高。
為去除冗余,降低維護(hù)工作量,最初按照數(shù)據(jù)表的維度將每一個(gè)表作為一個(gè)緩存。作為ES緩存可以采用這個(gè)方案,但是對(duì)于Redis緩存,這種緩存方式卻帶來(lái)了很大的麻煩。
數(shù)據(jù)庫(kù)表設(shè)計(jì)為保證強(qiáng)一致性,建表的時(shí)候嚴(yán)格依照范式,數(shù)據(jù)中很少有冗余,表也切的很小,查詢時(shí)通過(guò)聯(lián)合查詢來(lái)獲取整體數(shù)據(jù)。但Redis沒(méi)有聯(lián)合查詢的功能,因此不得不多次調(diào)用不同的緩存,多次調(diào)用大大降低了性能。對(duì)于查詢而言,數(shù)據(jù)庫(kù)會(huì)進(jìn)行一些反范式操作。既然Reids緩存能夠支撐查詢,那么也可以做一定的冗余把這些關(guān)聯(lián)數(shù)據(jù)作為一個(gè)整體對(duì)象緩存起來(lái)。
對(duì)于服務(wù)開(kāi)發(fā)人員而言,主要職責(zé)是根據(jù)環(huán)境變化,不斷的進(jìn)化服務(wù)模型。服務(wù)開(kāi)發(fā)人員維護(hù)一套***、最完整的服務(wù)模型并將模型開(kāi)放出來(lái);服務(wù)調(diào)用者,特別是只獲取服務(wù)數(shù)據(jù)的調(diào)用者完全可以通過(guò)對(duì)服務(wù)完整模型的自定義裁剪獲取自己所需要的數(shù)據(jù),各開(kāi)發(fā)人員只關(guān)注自己需要關(guān)注的地方,大大提高了工作效率。
3. 緩存構(gòu)建方案
面臨問(wèn)題:
- 服務(wù)緩存構(gòu)建與變更屬于非核心流程,所以只能異步執(zhí)行,通過(guò)MQ的方式與主流程解耦。
 - 服務(wù)屬性修改入口眾多,通過(guò)MQ會(huì)出現(xiàn)操作重排序問(wèn)題。
 - 服務(wù)屬性修改入口眾多,每次修改或添加入口都必須跟著修改,業(yè)務(wù)侵入性強(qiáng)。
 - 發(fā)送MQ的時(shí)機(jī),事務(wù)中影響事務(wù)性能,當(dāng)事務(wù)回滾時(shí)還需要發(fā)送補(bǔ)償;事務(wù)后又無(wú)法保證一定能發(fā)送。
 
解決方案:
- 采用binlake的方式進(jìn)行異步緩存構(gòu)建,與主流程解耦。 Binlake是京東一款通過(guò)解析MySQL的binlog日志,并通過(guò)MQ隊(duì)列進(jìn)行解析受數(shù)據(jù)變更事件傳遞的數(shù)據(jù)異構(gòu)產(chǎn)品。
 - 數(shù)據(jù)庫(kù)是功能修改后唯一進(jìn)行數(shù)據(jù)持久化的地方,僅需監(jiān)控?cái)?shù)據(jù)庫(kù)修改,就可獲知所有的服務(wù)屬性修改,不再需要跟著業(yè)務(wù)走,也不用擔(dān)心操作重排序。
 - 事務(wù)提交才能產(chǎn)生binlog日志,binlog的產(chǎn)生標(biāo)志數(shù)據(jù)修改出于確定狀態(tài),不會(huì)出現(xiàn)回滾,解決MQ發(fā)送時(shí)機(jī)的問(wèn)題。
 - Binlog事件通過(guò)MQ發(fā)送,發(fā)送不成功不修改日志偏移量,下次繼續(xù)發(fā)送。接收隊(duì)列為回執(zhí)確認(rèn)式隊(duì)列,消費(fèi)完成回執(zhí)確認(rèn)前會(huì)不斷進(jìn)行重試,解決發(fā)送丟失或接收后丟失問(wèn)題。
 
初期采取直接解析binlog報(bào)文,按照消息內(nèi)容更新數(shù)據(jù)。為保證消費(fèi)順序性,必須只有一個(gè)隊(duì)列進(jìn)行消息傳遞,大大降低了效率,并埋下了單點(diǎn)的隱患。
解決方法是,MQ不作為數(shù)據(jù)變化的承載者,而是作為一個(gè)通知者。當(dāng)緩存構(gòu)造者接受到MQ的時(shí)候,從數(shù)據(jù)庫(kù)獲取***的服務(wù)屬性,更新到緩存中。通過(guò)拉式獲取完整的服務(wù)屬性數(shù)據(jù),保證了數(shù)據(jù)的完整性、一致性。而主動(dòng)拉取數(shù)據(jù),不限制于消息本身,也不需要保證消息順序性,***解決效率與單點(diǎn)問(wèn)題。在屬性被多次修改時(shí),更能在其他修改消息未接收到時(shí),就已經(jīng)拉取到***數(shù)據(jù)更新了緩存數(shù)據(jù),進(jìn)一步提高了實(shí)時(shí)性。
***,單向事件觸發(fā)有很小的概率還是會(huì)發(fā)生數(shù)據(jù)不一致。解決辦法是,采用定時(shí)比對(duì)的方式,每個(gè)小時(shí)(可調(diào)整)通過(guò)時(shí)間戳比對(duì)當(dāng)日數(shù)據(jù)與緩存數(shù)據(jù)差異,進(jìn)行最終補(bǔ)償。
四、后記
解決了不同服務(wù)對(duì)相同資源的調(diào)用沖突,服務(wù)內(nèi)不同的場(chǎng)景使用不同的資源支撐,創(chuàng)建了統(tǒng)一緩存層擺脫對(duì)數(shù)據(jù)庫(kù)的依賴。使用不同的方法解決了當(dāng)統(tǒng)一緩存建立以后,如何使查詢擺脫了對(duì)數(shù)據(jù)庫(kù)的強(qiáng)依賴,服務(wù)性能得到了非常大的提升。
改造前支撐調(diào)用量:
改造后支撐調(diào)用量:
通過(guò)以上演進(jìn),“可用插件列表服務(wù)”并發(fā)性能有了很大的提升。 2018年11.11零點(diǎn)調(diào)用量10分鐘內(nèi)陡增6倍,平穩(wěn)度過(guò)。
作者簡(jiǎn)介:張俊卿,研發(fā)老兵,熱愛(ài)技術(shù),喜歡挑戰(zhàn)。熟悉各種開(kāi)源框架,對(duì)大型分布式系統(tǒng)有豐富的架構(gòu)、設(shè)計(jì)經(jīng)驗(yàn)。性能卓越、設(shè)計(jì)優(yōu)雅是其一生的追求。
【本文來(lái)自51CTO專欄作者張開(kāi)濤的微信公眾號(hào)(開(kāi)濤的博客),公眾號(hào)id: kaitao-1234567】




























 
 
 






 
 
 
 