聶理多 趙衛東 婁聰
摘要:該文參考阿里巴巴等大型電商系統的架構設計,提出了基于Oracle數據庫的讀寫分離方案,針對電商所獨有的業務場景,也即讀多寫少,進行數據訪問層在高并發場景下的性能優化;同時基于JavaEE Spring Boot對后端服務進行基于領域模型的服務化劃分,配套Consul 的服務治理與發現,以及Kafka消息隊列對服務進行解耦與削峰。從而使得整個系統具有在企業業務發展過程中進行平緩擴容的能力,以及高可用,高并發的體系結構。
關鍵詞:Oracle;服務化;高并發;讀寫分離;電子商務
中圖分類號:TP311? 文獻標識碼:A
文章編號:1009-3044(2021)29-0033-04
1背景
近年來,電子商務的發展在我國愈演愈烈,在我們的日常生活中,諸如阿里巴巴,京東,亞馬遜這樣的電子商務平臺早已耳熟能詳,各種促銷活動鋪天蓋地。但隨之而來的,是平臺背后的服務支撐體系的巨大的挑戰,無論是在技術,運營還是相關配套的物流,在線支付等方面。目前企業級市場成熟的Java EE Spring Boot微服務框架,提供基于領域模型的服務化系統切分與解耦,各服務間職責分明,可以由不同的開發組進行維護與支持,從而非常適合企業級項目在業務、人員增長的情況下的精細化,流水線化的分工[1];同時針對電商數據庫讀多寫少的業務場景,配合Oracle數據庫的讀寫分離技術,進行數據的冗余備份,提供數據訪問層的高并發,高可用性支持[2];針對電商用戶端界面,我們引入BFF(Backends For Frontends)層,BFF層主要用于模板渲染,使用前端工程師友好的Nodejs 進行編寫,并與后端服務通過RPC進行通信。完善并實現各架構模式,技術落地,同時對體系關鍵流程節點與鏈路進行局部的壓力測試。
通過合理的體系架構,充分利用企業的各種資源,每個角色都做自己最擅長的領域,從而實現最快的開發,最高的效率;在體系的每一層,都提供高并發,高可用性支持,滿足企業級應用對安全的需求。
2基于數據庫讀寫分離的架構設計
本設計在數據存儲方面使用Oracle數據庫并基于其 Data Guard技術進行主從冗余備份,配置一臺讀實例和一臺寫實例;在JavaEE的數據訪問層使用Spring Dynamic DataSource Rout?ing進行應用層的讀寫分離。
2.1 Oracle Data Guard
Oracle Data Guard 簡單來說就是利用某種同步機制(SQLApply 或者 Log Apply)將數據實時或者近實時的同步至另一臺服務器,原理是將主庫上的變化在備庫上重做一遍。因為其具有穩定、可靠、維護簡單的特點,所以在生產環境下被廣泛使用,如數據容災、數據庫遷移、數據庫升級、SQL 審查測試等場景。
Oracle Data Guard架構場景如圖1所示,主庫數據同步至同機房的備庫1,備庫2和備庫3有可能是位于同城機房或異地機房,通過網絡進行日志的傳輸與同步。
2.2數據訪問層設計
針對Oracle數據庫的讀寫分離配置,對應我們工程的數據訪問層也要做相應的調整,從而支持多數據源,以及區分讀庫與寫庫。由于我們的工程是基于 Spring加Hibernate進行構建的,故我們也以此進行說明。如圖2所示,我們有兩個Oracle數據實例,也即Spring數據訪問層的兩個數據源,Oracle主從實例間通過上面所提到的Data Guard技術進行冗余。應用將寫操作執行到主庫,將讀操作執行到冗余庫,完成讀寫分離的應用層設計。那么現在所要解決的就是從應用到數據庫實例之間的訪問與分離的問題。
3系統功能
3.1用戶中心
用戶中心是電商運營關鍵的一環,因為其承載了用戶幾乎所有的記錄,對于電商精細化運營非常關鍵。
用戶中心主要負責維護用戶模型,用戶收藏模型,用戶設置模型。
如圖3為用戶中心表結構,用戶表主要維護用戶基本信息(USERS)、用戶地址表(USER_ADDRIESS)主要維護用戶添加的地址信息,用于用戶下單快遞配送、用戶收藏表(USER_COL? LECTION)維護用戶收藏的商品信息。
對外提供基于用戶模型,用戶地址模型,用戶收藏模型的RPC調用接口。
3.2商品中心
商品中心管理的是電商的核心內容,即售賣的商品,其主要負責新品立項、商品上下架、類目管理等。
如圖4為商品中心表結構,商品中心主要維護SPU與SKU 表,然后是圍繞SPU與SKU 的一系列的屬性擴展表。其一維護規格,規格與SKU對應,直接決定價格,如蘋果手機的內存,也就是圖4-3的規格表(SPECIFICATION)、規格選項表(SPECOP? TION)以及SKU規格關聯表(SKUSPECBIND),規格表描述規格詳細信息,規格選項表主要是商品所對應的具體的規格的值,如64G 內存,128G 內存;其二是維護屬性,可以認為屬性是不決定價格的規格,如衣服的尺碼,由屬性表(ATTRIBUTE)、屬性選項表(ATTRIBUTEOPTION)和 SKU 屬性關聯表(SKUAT? TRIBIND)組成,各表的邏輯和作用和規格表組類似;然后是商品分類,由于商城為網上商超場景,并非諸如淘寶、京東的大型市場,故只有一級類目,但區分前后臺類目,前端類目經常調整,后端類目相對穩定,涉及前臺類目表(FEDCATEGORY),后臺類目表(BEDCATEGORY),前后臺類目關聯表(BEDFEDCAT? BIND),前臺類目表存放前臺類目,后臺類目表存放后臺類目,關聯表存放前后臺關聯關系。最后,品牌表維護商品品牌。
對外提供基于商品前后臺類目,SPU,SKU模型的 RPC調用接口。
3.3數據庫
本設計在數據存儲方面使用Oracle數據庫并基于其 Data Guard技術進行主從冗余備份,配置一臺讀實例和一臺寫實例;在JavaEE的數據訪問層使用Spring Dynamic DataSource Rout?ing進行應用層的讀寫分離。
Oracle Data Guard 簡單來說就是利用某種同步機制(SQL Apply 或者 Log Apply)將數據實時或者近實時的同步至另一臺服務器,原理是將主庫上的變化在備庫上重做一遍。因為其具有穩定、可靠、維護簡單的特點,所以在生產環境下被廣泛使用,如數據容災、數據庫遷移、數據庫升級、SQL 審查測試等場景。
Oracle Data Guard架構場景如圖5所示,主庫數據同步至同機房的備庫1,備庫2和備庫3有可能是位于同城機房或異地機房,通過網絡進行日志的傳輸與同步。
4關鍵技術
4.1 Spring 多數據源配置
基于Java 的javax.sql.DataSource,Spring 為我們提供了Ab?stractRoutingDatasource。通過AbstractRoutingDatasource,我們可以實現在運行時針對不同的操作來決定所使用的數據源。繼承AbstractRoutingDatasource抽象類,并實現determineCur?rentLookupKey方法,返回lookupKey,在每次Spring需要數據池中的連接時,Spring都會去調用這個方法,從而決定當前該使用哪個數據源。代碼如下所示:
public class DynamicRoutingDataSource extends Abstrac?tRoutingDataSource {
@Override
protected ectdetermineCurrentLookupKey(){
return DynamicRoutingContextHolder.getRouteStrategy();}
}
同時,這里有一點需要強調的是,我們Java應用層的每一個事務,其所對應的都是一個獨立的線程。這也是我們后面配置的關鍵。
數據源路由策略枚舉:
public enumRoutingStrategy {
Master(true, "master"), Slave(false, "slave");
private boolean write;
private String key;
RoutingStrategy(boolean write, String key){
this.write = write;
this.key = key;
}
public booleanisWrite(){
return write;
}
public String getKey(){
return key;
}
}
數據源策略set,get類:
package com.lancelou.gt.yhooms.ds;
import org.springframework.util.Assert;
public class DynamicRoutingContextHolder {
private static final ThreadLocal<RoutingStrategy> context? Holder =
new ThreadLocal<>();
public static void setRouteStrategy(RoutingStrategy custom?erType){
if(customerType == null){
throw new NullPointerException();
}
contextHolder.set(customerType);
}
public static RoutingStrategygetRouteStrategy(){
return (RoutingStrategy) contextHolder.get();
}
public static void clearRouteStrategy(){
contextHolder.remove();
}
}
至此,我們已經配置了多數據源,且能夠設置當前事務所屬線程的數據源,通過業務層手動調用DynamicRoutingContext? Holder類來設置當前所使用的數據源策略即可。但這種方式存在不足,一是數據源設置代碼不應該存在于業務代碼中,二是我們還需要進行異常處理。
4.2 Spring Aop加注解優化讀寫數據源配置
其實我們需要做的,無非是在我們的Service層業務代碼執行的時候,設置線程當前的數據源策略,在代碼運行結束后,清除線程當前的策略。這讓我們想到Spring 的另一強勢:Aop(面向切面編程)。 Spring 攔截器ReadOnlyConnectionInterceptor,其實具體的邏輯也就是我們開始提到的,這里不贅述:
public class ReadOnlyConnectionInterceptor implements Or?dered {
private int order;
@Value("20")
public void setOrder(int order){
this.order = order;
}
@Override
public int getOrder(){
return order;
}
@Pointcut(value="execution(public **(..))")? public void anyPublicMethod(){ }?????? @Around("@annotation(readOnlyConnection)")
public ect proceed(ProceedingJoinPointpjp, ReadOnly? Connection readOnlyConnection) throws Throwable {
try {
DynamicRoutingContextHolder. setRouteStrategy(Rout?ingStrategy.Slave);
ect result = pjp.proceed();???????????? DynamicRoutingContextHolder.clearRouteStrategy(); return result;
} finally {
// restore state
DynamicRoutingContextHolder.clearRouteStrategy();
}
}
}
對應的readOnlyConnection注解接口:?????? @Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}
至此,讀寫分離的數據層訪問設計配置完畢,在業務代碼中,我們只需通過readOnlyConnection注解來對需要訪問從庫的Service方法進行注解即可;對于需要訪問主庫的Service方法,我們無需做任何處理,默認即可。
4.3后臺技術選型
作為管理系統,我們選擇業界較為成熟的中后臺系統解決方案Ant Design。Ant Design是一個服務于企業級產品的設計體系,基于“確定”和“自然”的設計價值觀和模塊化的解決方案,讓設計者專注于更好的用戶體驗。Ant Design 為一系列的中后臺設計需求提供支持,內部包含一系列基礎的;業務弱相關的;開箱即用的組件,極大地方便了中后臺系統的設計與開發。
Ant Design基于React,在本設計管理系統的技術選型中,毫不猶豫地選擇了React,正是因為上面提到的管理系統的一系列業務場景,也正是諸如 React這類庫所發揮作用的地方。總結來說管理系統有下列這些特點,抑或場景,使得我們能夠部署React這樣的庫:
管理系統非常適合做SPA(單頁面應用),因為系統大多運行于內網,于有線的PC機上,對網絡的加載,頁面的性能相對來說要求較低;
管理系統的普遍的模式是,菜單、界面、操作,每一個操作界面內部的DOM變更頻換,那么此時基于React 的組件狀態管理以及聲明式編程就有了非常大的優勢;
5軟件測試
軟件測試是軟件開發流程中非常重要的一個環節[3],本設計在測試方法上使用了單元測試以及壓力測試,分別用于測試系統各模塊的邏輯與功能的完整性、可靠性以及整個系統架構的響應能力。
單元測試:又稱為模塊測試, 是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作[4]。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對于面向對象編程,最小單元就是方法,包括基類(超類)、抽象類或者派生類(子類)中的方法[5]。我們一般會針對后端項目的 Service 和 Dao進行單元測試,針對每一個 Service 和Dao都會編寫測試類,當我們項目編寫完成時,我們亦可以通過運行項目的所有單元測試來檢驗項目的測試覆蓋率。
壓力測試:壓力測試是檢驗軟件性能表現的一項重要測試技術,軟件能夠承載多大的并發;峰值是多少;響應時間處在何種水平等等都能通過壓力測試進行較為準確的量化。是軟件測試環節,特別是高并發業務場景流程測試中不可或缺的一部分。
針對本設計的讀寫分離方案,我們使用WebBench進行了在開啟和關閉讀寫分離的場景下的壓力測試。如圖6所示,為兩種場景3000并發下的性能表現,我們可以看到,在3000并發量下關閉讀寫分離的性能表現(每分鐘響應請求數和每秒傳輸字節數)相對于開啟的表現有降低,且有兩個失敗的請求。可能不是很明顯,我們加大并發量。
如圖7所示,為8000并發量時兩種場景的對比,可以看到,讀寫分離的應用依然對高并發的處理表現出了明顯的優勢。
6結束語
電子商務的發展是對技術體系架構考驗非常大的一個場景,其實原因我們也很容易去考究,無不外乎并發高,體量大,業務線繁雜等等。基于JavaEE的電子商務系統服務化的實現,可以作為以及與之配套的服務治理與服務發現的應用;同時基于 Oracle 數據庫 Data Guard 技術的讀寫分離方案以及基于 Spring Dynamic DataSource Routing 應用層配套方案;基于 BFF 模式的前后端分離,后端服務化,也可作為及其配套的RPC與 NodeJS企業級應用。并且完善并實現了各架構模式,技術落地,同時對體系關鍵流程節點與鏈路進行局部的壓力測試,我們的系統有極其穩定的性能表現,發展潛力巨大。可以肯定,基于高并發,高可用,讀寫分離的分布式架構是未來軟件系統發展的方向,未來發展前景極好。
參考文獻:
[1] 李軍 . 高并發 Web 系統的設計與優化[D]. 北京:北京交通大學,2009.
[2] 劉浩.基于負載均衡的存儲架構研究與應用[D].濟南:山東大,2011.
[3] 羅健萍.高校行政辦公自動化系統的設計與實現[D].成都:電子科技大學,2012.
[4] 閆煜瑤.用友金融商業平臺設計與實現[D].北京:北京交通大學,2019.
[5] 趙曉東.基于Google云的B2C網站后臺管理模塊實現[D].成都:電子科技大學,2011.
【通聯編輯:謝媛媛】