一、前言
隨着用戶量級的快速增長,vivo官方商城v1.0的單體架構逐漸暴露出弊端:模塊愈發臃腫、開發效率低下、性能出現瓶頸、系統維護困難。
從2017年開始啟動的v2.0架構升級,基於業務模塊進行垂直的系統物理拆分,拆分出來業務線各司其職,提供服務化的能力,共同支撐主站業務。
商品模塊是整個鏈路的核心,模塊的增多嚴重影響系統的性能,服務化改造勢在必行。
本文將介紹vivo商城商品系統建設的過程中遇到的問題和解決方案,分享架構設計經驗。
二、商品系統演進
將商品模塊從商城拆分出來,獨立為商品系統,逐漸向底層發展,為商城,搜索,會員、營銷等提供基礎標准化服務。
商品系統架構圖如下:
前期商品系統比較雜亂,包含業務模塊比較多,如商品活動業務、秒殺業務,庫存管理,隨着業務的不斷發展,商品系統承載更多的業務不利於系統擴展和維護。
故思考逐漸將商品業務逐漸下沉並作為最底層、最基礎的業務系統,並為眾多調用方提供高性能的服務,下面介紹商品系統的升級歷史。
2.1 商品活動、贈品剝離
隨着商品活動的不斷增多,玩法多樣,同時與活動相關的額外屬性也相應增加,這些都並不是與商品信息強關聯,更偏向於用戶營銷,不應該與核心商品業務耦合在一起,故將其合並入商城促銷系統。
贈品不僅僅是手機、配件,有可能會是積分、會員等,這些放在商品系統都不合適,也不屬於商品模塊的內容,故同步將其合並入商城促銷系統。
2.2 秒殺獨立
眾所周知,秒殺活動的特點是:
-
限時:時間范圍很短,超過設置的時間就結束了
-
限量:商品數量很少,遠低於實際庫存
-
訪問量大:價格低,可以吸引非常多的用戶
基於以上特性,做好一個秒殺活動不是一蹴而就,由於系統資源共享,當突發的大流量沖擊會造成商品系統其他業務拒絕服務,會對核心的交易鏈路造成阻塞的風險,故將其獨立為單獨的秒殺系統,單獨對外提供服務。
2.3 代銷系統成立
我們商城的主要銷售品類還是手機以及手機配件等,商品的品類比較少,為了解決非手機商品品類不豐富的問題,運營考慮與知名電商進行合作,期望引入更多的商品品類。
為了方便后續擴展,以及對原有系統的不侵入性,我們經過考慮專門獨立出一個子系統,用於承接代銷業務,最后期望做成一個完備平台,后續通過提供開放API的方式讓其他電商主動接入我們業務。
2.4 庫存剝離
庫存管理的痛點:
-
由於我們的庫存都是到商品維度,僅僅一個字段標識數量,每次編輯商品都需要為商品調整庫存,無法動態實現庫存管理;
-
同時營銷系統也有自己活動庫存管理機制,入口分散,關聯性較弱;
-
可售庫存和活動庫存管理的依據都是實際庫存,造成容易配置錯誤。
基於以上痛點,同時為了更方便運營管理庫存,也為未來使用實際庫存進行銷售打下基礎,我們成立庫存中心,並提供以下主要功能:
-
與ecms實際庫存進行實時同步;
-
可以根據實際庫存的倉庫分布情況,計算商品的預計發貨倉庫和發貨時間,從而計算商品預計送達時間;
-
完成低庫存預警,可以根據可用庫存、平均月銷等進行計算,動態提醒運營訂貨。
三、挑戰
作為最底層的系統,最主要的挑戰就是具備穩定性,高性能,數據一致性的能力。
3.1 穩定性
-
避免單機瓶頸:根據壓測選擇合適的節點數量,不浪費,同時也能保證溝通,可以應對突發流量。
-
業務限流降級:對核心接口進行限流,優先保證系統可用,當流量對系統壓力過大時將非核心業務進行降級,優先保證核心業務。
-
設置合理的超時時間:對Redis、數據庫的訪問設置合理超時時間,不宜過長,避免流量較大時導致應用線程被占滿。
-
監控&告警:日志規范化,同時接入公司的日志監控和告警平台,做到主動發現問題並及時。
-
熔斷:外部接口接入熔斷,防止因為外部接口異常導致本系統受到影響。
3.2 高性能
多級緩存
為了提升查詢速度,降低數據庫的壓力,我們采用多級緩存的方式,接口接入熱點緩存組件,動態探測熱點數據,如果是熱點則直接從本地獲取,如果不是熱點則直接從redis獲取。
讀寫分離
數據庫采用讀寫分離架構,主庫進行更新操作,從庫負責查詢操作。
接口限流
接入限流組件, 直接操作數據庫的接口會進行限流,防止因為突發流量、或者不規范調用導致數據庫壓力增加,影響其他接口。
不過早期也踩過一些坑:
1、商品列表查詢造成redis key過多,導致redis內存不夠的風險
由於是列表查詢,進行緩存的時候是對入參進行hash,獲取唯一的key,由於入參商品較多,某些場景下入參是隨時變化的,根據排列組合,會造成基本每次請求都會回源,再緩存,可能造成數據庫拒絕服務或者redis內存溢出。
方案一:循環入參列表,每次從redis獲取數據,然后返回;
這個方案解決了key過多導致內存溢出的問題,但是很明顯,它增加了很多的網絡交互,如果有幾十個key,可想而知,對性能會有不小的影響,那有什么其他辦法能減少網絡交互呢,下面我們看方案二。
方案二:我們通過對原有的Redis 組件進行增強,由於Redis集群模式不支持mget,故我們采用pipeline的方式實現,先根據key計算出其所在的slot,然后聚合一次性提交,這樣每個商品數據只需緩存一次即可,同時采用mget也大大提升了查詢速度。
這就即解決了key值過多的問題,也解決了方案一中多次網絡交互的問題,經過壓測對比,方案二比方案一性能提升50%以上,key越多,效果越明顯。
2、熱點數據,導致redis單機瓶頸
商城經常有新品發布會,發布會結束后會直接跳轉到新品商詳頁,此時新品商詳頁就會出現流量特別大且突發、數據單一,這就導致Redis節點負載不平衡,有些10%不到,有些達到90%多,而一些常規的擴容是沒有效果的。
針對熱點問題我們有以下解決方案:
-
key的散列,將key分散到不同的節點
-
采用本地緩存的方式
開始我們采用的是基於開源的Caffeine完成本地緩存組件,本地自動計算請求量,當達到一定的閥值就緩存數據,根據不同的業務場景緩存不同的時間,一般不超過15秒,主要解決熱點數據的問題。
后來替換成我們自己研發的熱點緩存組件,支持熱點動態探測,熱點上報,集群廣播等功能。
3.3 數據一致性
1、對於Redis的數據一致性比較好解決,采用“Cache Aside Pattern”:
對於讀請求采用先讀緩存,命中直接返回,未命中讀數據庫再緩存。對於寫請求采用先操作數據庫,再刪除緩存。
2、由於庫存剝離出去,維護入口還是在商品系統,這就導致存在跨庫操作,平常的單庫事務無法解決。
開始我們采用異常捕獲,本地事務回滾的方式,操作麻煩點,但也能解決這個問題。
后來我們通過開源的seata完成分布式事務組件,通過改寫代碼引入公司的基礎組件,目前已經接入使用。
四、總結
本篇主要介紹商城商品系統如何進行拆分、並慢慢下沉為最基礎的系統,使其職責更加單一,能夠提供高性能的商品服務,並分享在此過程中遇到的技術問題和解決方案,后續會有庫存系統的演進歷史、分布式事務相關內容,敬請期待。
作者:vivo官網商城開發團隊-Ju Changjiang