電商系統服務拆分實戰


技術發展到這個年代,微服務幾乎是標配,人們對單體應用的概念反而模糊了。但整個演變過程還是要稍微過一下。

一個系統最開始的模樣很可能就是個單體,tomcat一啟動就是整個系統。

一台機撐不住了便需要橫向擴展加機器,開始了分布式的第一步。這時需要保持用戶登錄態,便有了集中式會話管理,方案若干種,會話粘連、ip哈希、tomcat集群同步、集中式,現在基本統一成spring-session-redis。

慢慢地前端也分離出來了,在nginx反向代理的基礎上做靜態加速,頁面交給nginx和CDN,動態數據轉發給后端。

逐漸地,數據庫成了瓶頸,於是把壓力分出去,列表查詢交給es,庫存查詢交給redis等等。

隨着業務的增長,服務器一個大單體撐不住了,光是加機器也不行,代碼臃腫、啟動很慢、各業務線變更沖突多,開發效率和運行效率都低下,需要把整個應用拆小出來了。

拆分目標

拆分時也要考慮其副作用的,比如拆分模塊多了代碼不好找、比如多幾層轉發和對象復制影響開發效率等等。因此拆分要緊扣這目標逐步展開。

模塊復用:拆分過程中要盡量沉淀出可復用的基礎模塊,例如用戶模塊、商品模塊、訂單模塊等等,這些基礎的輪子能有效提高后續新業務的開發效率。

解耦:比方說商品加一個字段會導致交易下單代碼會報錯必須跟着改,這種是嚴重且不合理的耦合,因而要盡量把拆分出來的模塊的職責邊界定義清楚。

擴展性:一般來說微服務不會有擴展性問題,也就是說一個服務拆出來一般是無狀態的可以簡單通過加機器擴展的,而相應的數據庫也要適當考慮數據規模與擴展。

中台化:中台建設和服務拆分不是同個維度的話題,但服務拆分和模塊可編排可配置是中台的基礎,因而在服務拆分過程中可以適當考慮配置化,為后續做鋪墊。

拆分實戰

總覽與方向

總覽圖如下,是現階段分后的大概划分。

拆分前的商品系統和交易系統是兩個大“單體”,代碼的包結構有的按端划分、有的按域模塊划分、有的按不同行業業務划分,十分混亂。

 

拆分是個漫長演進過程,大體上會按三個方向推進:

水平方向按域拆分,即會員、商品、商戶、交易與訂單、內容、營銷、TMS、WMS、庫存等等。

上層業務則逐漸拆分出業務編排和行業端等模塊,組織聚合各域基礎服務,承載上層差異化的業務。

垂直方向可進一步分層拆分,例如商品分層出商品管理、行業維度商品聚合、搜索與緩存等。

本期重點討論的是商品服務的拆分,如下圖所示。

 

業務梳理過程

服務拆分前最重要的便是梳理出主要業務,以及模塊邊界。具體以商品服務來討論的話:

商品主要有商品管理CRUD、查詢服務(按編碼,主要供信息聚合)、類目、規格與屬性、上下架、消費者端的列表與詳情(搜索與緩存)、庫存、價格。

其中,商品SKU、SPU、類目、規格與屬性是比較通用的能力,適合拆分出來。

上下架、搜索與緩存等比較模棱兩可,要視具體業務而定,要看具體中間件是否能打通,要看搜索的文檔是否足夠通用,而答案往往是否定的,不同的行業不同端的列表組織方式並不通用。

至於庫存與價格,我本人是強烈反對放到商品服務的,業務單一的初期無所謂,可當業務足夠豐富了之后,光庫存二字就可能是自己一個龐大的系統。且不提供應鏈中的倉庫存、供應庫存、在途庫存,光是討論銷售庫存,也都還區分SPU庫存、SKU庫存、商家銷售庫存、區域門店銷售庫存,B2C模式用的是商家銷售庫存,O2O模式關心的卻是門店庫存,跟業務關聯十分緊密。價格也類似。

因此,確定下來模塊的主要能力是SKU、SPU、類目、屬性的基礎服務。

商品主維度

業務方面這里可以展開多講一點,關於商品主維度。(當然了,如果業務足夠單一,也不需要這方面的討論。)商品主維度要討論的是,如何唯一確定一個商品。

首先是商品在各生命周期中的變化:后台發布商品、往上游是供應商與采購的商品(也可以理解為批次)、往下游下單時會生成商品快照。那么,上一批采購跟這一批在另一供應商采購的商品是否同個商品呢?這個例子倒沒有太大爭議,是同一個基礎商品,但是是不同供應商不同批次的商品。

再者是行業模式特性的變化:比如特賣分早晚兩檔,兩檔有各自的促銷標語和價格,又比如兩個門店賣同一款商品,但圖片和價格又不太一樣。所以哪個維度才是基礎商品呢?如果我整個平台都是特賣的分場次的,以場次商品作為商品的主要維度又有何不可。當然了,分成基礎商品和場次商品兩層會更合理點,基礎商品還可以支撐其他模式。

 

所以,最極端情況下,商品是需要支撐一品多商(商家和門店) 、一品多供(供應商)、一品多區(區域,常見於快消行業)、一品多碼(條形碼,例如新舊包裝隨機發貨)的不同維度關聯,這個倒沒有標准答案的,選擇適合自己行業的維度和復雜度即可。

開發過程

編碼的套路有兩種思路可供參考。

如果是原有代碼本來就有包拆分和模糊的模塊概念,那么只要把相關的包遷移出來即可,像商品這種偏固定的、不太有流程變化的模塊特別適合。

另一種則相對通用,是先按上一步梳理出對應的功能進一步細化成服務和接口,上層對接口接入和適配,下層則對接口做實現。

等價替換

服務拆分其實是一種重構,也就是說對上層業務來說是等價替換的。因此,單元測試可以做對比測試,集成測試可以直接跑自動化測試。

選擇合理的方式真的可以讓單元測試又快又穩。如圖,將服務暴露成不同的版本(這里使用的是dubbo,用version和group都可以做版本隔離),便可以在同個用例調用中直接對比調用結果。而結果對照甚至可以直接用json的equals方法比對,字段是有穩定順序的。

不過主要還是用於冪等接口例如查詢,對於插入操作仍需要人肉比對。

 

 

數據遷移

數據遷移也是個頭疼的問題。這里探討幾種不同級別的遷移方案。

方案1.0,停服務遷移。完全停掉相關服務,mysql數據全量遷移(全量很快的就幾分鍾)。

方案1.1,忽略丟數據直接遷移。認為這段時間不會有操作(新建商品確實很低頻),直接全量復制直接發布重啟。

方案1.2,停部分服務遷移。用動態開關控制相關服務都降級為不可用,然后數據遷移,然后發布重啟,然后關閉降級開關從而恢復服務。

以上方案1系列都會有明顯的不可用時間,不嚴謹,但優點是操作簡單。回滾也簡單。

方案2,數據庫增量同步。連同全量數據全都當成增量,持續同步到新庫,直到服務發版,舊庫不再更新。正向發版倒是比較嚴謹,只要處理下id生成器不要沖突即可。逆向回滾時很麻煩,得專門考慮補增量數據。

方案3,雙寫,更新操作在新舊庫都要寫入(可開關控制),查詢操作在新庫查詢(可開關切換)。這種方案要解決三個問題,一個是雙寫部分失敗怎么辦,不存在事務回滾這一說,恐怕只能人工排查補救;第二是,全量數據補齊時是否會與增量沖突,這個也是需要處理下id生成器。第三是非冪等的接口重復操作的問題,例如庫存保存在redis上且每次都是加1減1這種增量操作,如果雙寫兩邊都加1可能導致最后結果是加2,需要控制只寫入一次。回滾則簡單了,只需要開關控制。

行業數據隔離

不同行業的商品數據是有不同特性的:比如服裝快時尚行業每天上新2000個sku;比如快消行業,同一個基礎商品,可能會衍生出不同區域不同渠道門店,而訪問的熱度可能是跟着商品走也可能是跟着渠道走;又比如生鮮行業,商品的訪問熱度跟季節有很強關聯。

隨着行業模式增多(這個倒是很慢),以及行業內商品數據的增多,必定要面臨進一步的拆分和加速。可以從兩個角度考慮。

第一是按行業做拆分,比如按行業分庫。

第二是在具體行業內,配置分表策略和緩存策略,比如生鮮行業,按id或時間哈希可以達到分散熱點的目的,而緩存則應該按季節標或促銷標。(不過,緩存還是盡可能是上層業務自己做。)

因此,表設計的時候可以預留行業字段可用於分庫、熱點字段可用於分表。當然了,這個也可以往后再加,視具體業務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM