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