京東服務市場高併發下 SOA 服務化演進架構
來這裡找志同道合的小夥伴!
京東服務市場是京東商家與第三方獨立軟體提供商(ISV)進行服務類的線上交易平臺。作為京東生態圈重要的一環,伴隨著整個京東的快速增長,也在快速的發展。隨著服務市場訪問、交易量指數級的增長,系統由原來的ALL IN ONE架構,快速的演進成為SOA架構。
木桶的容量由木桶最短的木板決定,高併發環境下,單個服務的效能決定了整個服務市場的效能。 “ 可用外掛列表服務 ”是服務市場的核心服務之一,優化該服務效能的過程,帶動整個服務市場服務架構的演進。
巨集觀的看,大到系統小到模組都由自身+外部依賴組成,效能優化主要從自身與外部依賴兩個方面來進行。
優化自身
單執行緒到多執行緒的升級,嘗試通過並行提高服務效能。
根據日誌分析,整體呼叫中“服務詳細資訊”佔用時間最多,並行雖然壓縮了一些可並行服務的呼叫時間,但對於無法並行的“服務詳細資訊”環節,依然沒有改善。要改善必須找到“商品服務”效能不高的原因。
可見自身優化能起一些作用,但外部依賴起著更決定性的作用。
解決外部依賴衝突
“商品服務”效能不高,這是為什麼呢?先從 “商品服務” 的依賴開始分析。單獨呼叫該服務,或壓測該服務,效能都不差,但為何線上效能卻不佳?
1、不同服務外部依賴資源衝突
對“商品服務”依賴的資源進行梳理,發現“商品服務”與“類目服務”使用相同資料庫資源,非呼叫高峰期資源足夠不相互影響,大併發環境下兩個服務開始爭奪資源。
將依賴資源分開,不同的服務使用不同的資源,通過呼叫不同的資料來源解決衝突。
2、相同服務外部資源依賴衝突
解決了兩個服務對資料庫資源的依賴衝突,效能有所提高,但效能總有很大的波動,排除其他服務外部資源的依賴衝突,看看“商品服務”自身對資源是如何使用的。
“商品服務”所有功能都單一的依賴資料庫資源。服務上線後,自身多個功能開始爭搶資料庫資源。
按使用場景進行外部依賴資源解耦:
1)為保證交易一致性,繼續採用MySQL。MySQL的 INNODB引擎長於 OLTP 線上事務處理,為了保證資料強一致性的場景繼續選擇使用MySQL資料庫。
2)客戶端登入使用者需要獲得最新的資料反饋,且有PIN這個固定的維度。查詢條件簡單,能符合KEY-VALUE方式,Redis很適合這個場景。
3)大前端非登入狀態下,訪問的使用者無須登入,有很大的訪問量,更多的是獲取服務的一些介紹。大資料量,可容忍一定程度的延遲,所以採用ES來進行查詢支撐。
4)外部系統希望獲得最新服務的變化,推的方式遠強於輪訓拉取的方式。通過MQ訂閱服務的變化情況。
5)有複雜計算,但對實時性要求不高,服務統計分析系統通過大資料平臺獲取資料進行分析。
建立統一的記憶體快取模型
計算機的世界裡沒有魔法,時間換空間、空間換時間是所有方案的基礎。
參考常用的MySQL INNODB引擎,為加快查詢速度會在記憶體中設定一塊記憶體作為緩衝區,將查詢結果從硬碟中載入到緩衝區,下次相同的查詢直接使用緩衝區資料。同樣的,如果要提高查詢響應速度,必須把服務資料快取到記憶體中。單機記憶體有限,無法容納所有資料,且伺服器重啟時整個記憶體重建所耗費的時間也是無法接受的,於是選擇用Redis與ES按照不同的使用場景來構造記憶體快取。
1、選擇主動快取
常規的快取方案:查詢構建+定期失效。對有大量重複查詢的環境效果很好,但在實際情況下,在某些場景卻無法發揮預想中的作用。
場景特徵:
1)每個使用者只會開啟一次客戶端,獲取一次外掛資訊,不會重複頻繁的去拉取列表。
2)訪問集中在8點到9點這個時間段。
使用被動快取的後果:
1)8點前Redis快取內是空的。
2)8點到9點,所有的列表資訊都是第一次獲取,查詢全部穿透快取直接打到資料庫。
3)8點到9點之間獲取外掛列表後做了外掛的續訂或許可權變更,由於快取定時失效,導致更新無法反饋,使用者不斷重新整理外掛列表直到快取失效獲取到更新結果。人為製造流量洪峰,Redis抗住的也是這些無用的人為重複呼叫量。
4)9點以後快取逐漸過期,不再被使用。
一個測試效能很好,實際卻沒有用的快取。
基於以上,快取層決定通過主動構建的方式建立快取。在資料修改後,將變化資料主動的載入到Redis快取中,快取不再設定過期時間。
有的服務每次獲取結果都要通過非常繁瑣的計算,如果這些繁瑣的計算集中在同一時間點,對於後端資源(資料庫)是非常大的負擔。
錯峰使用資源,把構建快取的過程分散在離散的呼叫中,集中使用時直接呼叫快取獲取最終結果。
上面提到過“類目服務”獲取類目層級列表需要多次查詢資料庫,這對資料庫是很大的負擔。
提前構建,在類目建立或類目變更時就重新構建類目層級列表,將結果存入快取,高峰期使用時直接獲取已構建完成的類目層級列表。
2、快取碎片化
系統使用一段時間後,由於業務系統對服務資料需求的不一致,服務開發人員開始為每個外部系統提供一塊主動快取。這些快取完全不具備通用性但又數量眾多。每次服務模型修改,研發人員都要花大量時間去維護這些不通用的快取。佔用的快取越來越多,但快取的使用率並不高。
為去除冗餘,降低維護工作量,最初按照資料表的維度將每一個表作為一個快取。作為ES快取可以採用這個方案,但是對於Redis快取,這種快取方式卻帶來了很大的麻煩。
資料庫表設計為保證強一致性,建表的時候嚴格依照正規化,資料中很少有冗餘,表也切的很小,查詢時通過聯合查詢來獲取整體資料。但Redis沒有聯合查詢的功能,因此不得不多次呼叫不同的快取,多次呼叫大大降低了效能。對於查詢而言,資料庫會進行一些反正規化操作。既然Reids快取能夠支撐查詢,那麼也可以做一定的冗餘把這些關聯資料作為一個整體物件快取起來。
對於服務開發人員而言,主要職責是根據環境變化,不斷的進化服務模型。服務開發人員維護一套最新、最完整的服務模型並將模型開放出來;服務呼叫者,特別是只獲取服務資料的呼叫者完全可以通過對服務完整模型的自定義裁剪獲取自己所需要的資料,各開發人員只關注自己需要關注的地方,大大提高了工作效率。
3、快取構建方案
面臨問題:
1)服務快取構建與變更屬於非核心流程,所以只能非同步執行,通過MQ的方式與主流程解耦。
2)服務屬性修改入口眾多,通過MQ會出現操作重排序問題。
3)服務屬性修改入口眾多,每次修改或新增入口都必須跟著修改,業務侵入性強。
4)傳送MQ的時機,事務中影響事務效能,當事務回滾時還需要傳送補償;事務後又無法保證一定能傳送。
解決方案:
1)採用binlake的方式進行非同步快取構建,與主流程解耦。 Binlake是京東一款通過解析MySQL的binlog日誌,並通過MQ佇列進行解析受資料變更事件傳遞的資料異構產品。
2)資料庫是功能修改後唯一進行資料持久化的地方,僅需監控資料庫修改,就可獲知所有的服務屬性修改,不再需要跟著業務走,也不用擔心操作重排序。
3)事務提交才能產生binlog日誌,binlog的產生標誌資料修改出於確定狀態,不會出現回滾,解決MQ傳送時機的問題。
4)Binlog事件通過MQ傳送,傳送不成功不修改日誌偏移量,下次繼續傳送。接收佇列為回執確認式佇列,消費完成回執確認前會不斷進行重試,解決傳送丟失或接收後丟失問題。
初期採取直接解析binlog報文,按照訊息內容更新資料。為保證消費順序性,必須只有一個佇列進行訊息傳遞,大大降低了效率,並埋下了單點的隱患。
解決方法是,MQ不作為資料變化的承載者,而是作為一個通知者。當快取構造者接受到MQ的時候,從資料庫獲取最新的服務屬性,更新到快取中。通過拉式獲取完整的服務屬性資料,保證了資料的完整性、一致性。而主動拉取資料,不限制於訊息本身,也不需要保證訊息順序性,完美解決效率與單點問題。在屬性被多次修改時,更能在其他修改訊息未接收到時,就已經拉取到最新資料更新了快取資料,進一步提高了實時性。
最後,單向事件觸發有很小的概率還是會發生資料不一致。解決辦法是,採用定時比對的方式,每個小時(可調整)通過時間戳比對當日資料與快取資料差異,進行最終補償。
後記
解決了不同服務對相同資源的呼叫衝突,服務內不同的場景使用不同的資源支撐,建立了統一快取層擺脫對資料庫的依賴。使用不同的方法解決了當統一快取建立以後,如何使查詢擺脫了對資料庫的強依賴,服務效能得到了非常大的提升。
改造前支撐呼叫量:
改造後支撐呼叫量:
通過以上演進,“可用外掛列表服務”併發效能有了很大的提升。 2018年11.11零點呼叫量10分鐘內陡增6倍,平穩度過。
作者簡介
研發老兵,熱愛技術,喜歡挑戰。熟悉各種開源框架,對大型分散式系統有豐富的架構、設計經驗。
效能卓越、設計優雅是其一生的追求。
RECOMMEND