得物業(yè)務(wù)參數(shù)配置中心架構(gòu)綜述
一、背景
現(xiàn)狀與痛點(diǎn)
在目前互聯(lián)網(wǎng)飛速發(fā)展的今天,企業(yè)對(duì)用人的要求越來(lái)越高,尤其是后端的開發(fā)同學(xué)大部分精力都要投入在對(duì)復(fù)雜需求的處理,以及代碼架構(gòu),穩(wěn)定性的工作中,在對(duì)比下,簡(jiǎn)單且重復(fù)的CRUD就顯得更加浪費(fèi)開發(fā)資源。目前scm供應(yīng)鏈管理頁(yè)面中,存在約77%的標(biāo)準(zhǔn)頁(yè)面,這些標(biāo)準(zhǔn)頁(yè)面里,還存在著很多類似的參數(shù)配置頁(yè)面,就是對(duì)某一個(gè)模型進(jìn)行增、刪、改、查、導(dǎo)入、導(dǎo)出進(jìn)行類似的操作,這種開發(fā)工作技術(shù)含量較低,而且相對(duì)耗費(fèi)人力。
什么是業(yè)務(wù)參數(shù)配置中心
參數(shù)配置中心,是一個(gè)能夠通過配置的方式,快速生成前端頁(yè)面以及配套增、刪、改、查、導(dǎo)入、導(dǎo)出服務(wù)的配置平臺(tái),它與得物內(nèi)部低代碼前端頁(yè)面平臺(tái)wizard相互集成,參數(shù)配置中心提供后臺(tái)增刪改查服務(wù),wizard輸出對(duì)應(yīng)的前端頁(yè)面代碼,并可以支持用戶自定義修改。
使用場(chǎng)景
- 針對(duì)讀多寫少的簡(jiǎn)單的單表的增刪改查;
- 業(yè)務(wù)中需要交給運(yùn)營(yíng)來(lái)修改的復(fù)雜ark配置(簡(jiǎn)單配置除外),可以嘗試使用業(yè)務(wù)參數(shù)配置中心接入,減少人為修改JSON可能產(chǎn)生的錯(cuò)誤,導(dǎo)致系統(tǒng)無(wú)法編譯進(jìn)而產(chǎn)生故障。
比如如下的JSON:
[{"position":"1","red":2.49,"blue":2.4,"green":1},
{"position":"2","red":2.49,"blue":2.4,"green":1},
{"position":"3","red":2.49,"blue":2.4,"green":1},
{"position":"4","red":2.49,"blue":2.4,"green":1},
{"position":"5","red":2.49,"blue":2.4,"green":1},
{"position":"6","red":2.49,"blue":2.4,"green":1},
{"position":"7","red":2.49,"blue":2.4,"green":1},
{"position":"8","red":2.49,"blue":2.4,"green":1}]
業(yè)務(wù)參數(shù)配置中心極速體驗(yàn)
1. 后臺(tái)服務(wù)搭建流程,以及數(shù)據(jù)錄入。
2. 數(shù)據(jù)讀取可以通過參數(shù)配置中心的SDK,輸入自己的業(yè)務(wù)入?yún)⒁约白约旱臉I(yè)務(wù)出參,SDK會(huì)自動(dòng)根據(jù)方案下的參數(shù)以及用戶的輸入條件,查詢出對(duì)應(yīng)的參數(shù)信息:
從上面的快速體驗(yàn)里可以看到很多名詞,你一定有會(huì)有下面的疑問:
二、整體架構(gòu)與原理
實(shí)現(xiàn)思路
首先我們對(duì)這種普通的頁(yè)面進(jìn)行初步剖析:頁(yè)面中總體包含搜索條件、靜態(tài)展示字段以及操作欄,搜索條件一般是靜態(tài)字段的子集,并且操作欄的功能一般都類似,所以為了能夠結(jié)構(gòu)化地構(gòu)造出這樣的頁(yè)面,我們可以將靜態(tài)展示字段進(jìn)行進(jìn)一步抽象:比如元素、維度、參數(shù)、方案、參數(shù)實(shí)例。
元素
構(gòu)成頁(yè)面的每一個(gè)業(yè)務(wù)字段,統(tǒng)稱元素,因?yàn)橛行┳侄问谴蠹页S玫模ū热鐐}(cāng)庫(kù),品牌,一級(jí)類目,省份等),它有自己的字段名稱,以及取值范圍。
維度
一條記錄一定有能夠標(biāo)注其唯一性的信息,可能是一個(gè)字段或者是多個(gè)字段,在參數(shù)中心里,能確定一條記錄唯一性的所有字段就叫做維度,維度這個(gè)概念在參數(shù)中心里很重要,它是不可變的。
參數(shù)
在業(yè)務(wù)發(fā)展過程里,可以改變值的字段,就叫參數(shù),也可以說(shuō)一條記錄里,除了維度,都可以叫做參數(shù)。
綜合維度和參數(shù),舉個(gè)例子,比如商品信息,商品ID就是維度,商品售價(jià)、折扣率就是參數(shù)。或者醫(yī)院掛號(hào)系統(tǒng),科室ID就是維度,掛號(hào)費(fèi),出診時(shí)間就是參數(shù)。
方案
一個(gè)參數(shù)方案它管理著一個(gè)場(chǎng)景下的業(yè)務(wù)配置,可以簡(jiǎn)單理解一個(gè)方案就代表著一個(gè)頁(yè)面,包含了上述我們說(shuō)的維度以及參數(shù),并且指定了可以指定哪些字段為搜索條件,哪些是必填字段,哪些字段可以多選。
參數(shù)實(shí)例
描述好方案并生成頁(yè)面后,實(shí)際產(chǎn)生的業(yè)務(wù)配置數(shù)據(jù),我們稱之為參數(shù)實(shí)例。
經(jīng)過剛才對(duì)頁(yè)面元素的解剖,大家會(huì)發(fā)現(xiàn)搭建一個(gè)這樣的頁(yè)面,猶如建房子一樣,維度與參數(shù)是最基礎(chǔ)的木料,創(chuàng)建方案就是設(shè)計(jì)建造的過程,參數(shù)實(shí)例就是一個(gè)個(gè)真實(shí)的房間,所以業(yè)務(wù)參數(shù)配置中心整體產(chǎn)品思路如下:
整體架構(gòu)
通過上文的介紹,我們介紹了業(yè)務(wù)參數(shù)配置中心最核心的概念,接下來(lái)我們看看整體的架構(gòu)設(shè)計(jì)。我們針對(duì)這些最核心的概念,來(lái)設(shè)計(jì)實(shí)現(xiàn)這些業(yè)務(wù)功能的架構(gòu)、核心包含領(lǐng)域模型、領(lǐng)域服務(wù)、應(yīng)用服務(wù)以及基礎(chǔ)設(shè)施層需要的存儲(chǔ)部件,以及外部可以整合的導(dǎo)入導(dǎo)出框架、日志框架(外部依賴的框架也可以自己實(shí)現(xiàn))、核心的元素維護(hù)、方案維護(hù),存儲(chǔ)設(shè)計(jì)好之后,我們就需要一個(gè)SDK,可以讓用戶訪問到我們的數(shù)據(jù)。
系統(tǒng)的實(shí)體關(guān)系圖如下:
通過上文我們可以初步了解到整體的架構(gòu)設(shè)計(jì),那么每一個(gè)子模塊我們?nèi)绾螌?shí)現(xiàn)?接下來(lái)我們分析更加細(xì)節(jié)的原理。
核心原理
如何設(shè)計(jì)存儲(chǔ)的細(xì)節(jié)是這個(gè)系統(tǒng)的一大挑戰(zhàn),因?yàn)榧纫骖欗?yè)面的靈活變動(dòng),也要兼顧數(shù)據(jù)整體的一致性不受影響,同時(shí)也要兼顧整體數(shù)據(jù)的查詢性能,下面的小節(jié)列出了所有這些核心的挑戰(zhàn)點(diǎn)。
存儲(chǔ)流程
每一個(gè)頁(yè)面的字段都不一樣,我們是怎么存儲(chǔ)的?
從上面的兩個(gè)頁(yè)面可以看到,因?yàn)轫?yè)面的字段變化多端,所以我們的思考是,必須采用抽象存儲(chǔ)的方式來(lái)應(yīng)對(duì),核心用一張 大寬表存儲(chǔ),其中包含很多抽象列,每一個(gè)抽象列在不同的方案下,業(yè)務(wù)含義不同。
同時(shí)把方案的元數(shù)據(jù):維度、參數(shù)、以及功能性設(shè)置(如每個(gè)字段是否可以刪除,是否需要多選)單獨(dú)存儲(chǔ),每個(gè)方案下的大寬表里的抽象列的業(yè)務(wù)含義,就存儲(chǔ)在這些元數(shù)據(jù)表中。
同時(shí)為了應(yīng)對(duì)大批量的查詢,我們引入了OLAP的數(shù)據(jù)庫(kù),對(duì)于在應(yīng)用內(nèi)部的單點(diǎn)查詢,我們走M(jìn)ySQL實(shí)現(xiàn),如果運(yùn)營(yíng)后臺(tái)針對(duì)某個(gè)字段做大批量查詢,則可以用OLAP數(shù)據(jù)庫(kù)來(lái)緩解查詢壓力。
下面是存儲(chǔ)的整個(gè)過程以及舉例:
SDK查詢流程
因?yàn)樵跇I(yè)務(wù)參數(shù)使用時(shí),各個(gè)業(yè)務(wù)方有自己的業(yè)務(wù)對(duì)象,所以我們?cè)赟DK中集成了反射的能力,可以避免用戶直接感知到底層的抽象存儲(chǔ),查詢的流程使用上比較簡(jiǎn)單,一共分為三步,第一步為自定義request,第二步自定義response,第三步調(diào)用SDK方法獲取參數(shù)實(shí)例,比如:
1. 定義request:
@Data
public class PinkDeviceCameraConfigRequest implements Serializable {
/**
* 配置類型
*/
private String configType;
/**
* 設(shè)備編號(hào)
*/
private String deviceNo;
}
2. 定義response
@Data
public class PinkDeviceCameraConfigResponse implements Serializable {
/**
* 配置類型
*/
private String configType;
/**
* 設(shè)備編號(hào)
*/
private String deviceNo;
/**
* 配置明細(xì)
*/
private List<CameraConfigDto> configValueList;
@Data
public static class CameraConfigDto implements Serializable {
private String position;
/**
* 白平衡(Red)
*/
private BigDecimal red;
/**
* 白平衡(Blue)
*/
private BigDecimal blue;
/**
* 白平衡(Green)
*/
private BigDecimal green;
/**
* 亮度(Brightness)
*/
private BigDecimal brightness;
/**
* 自動(dòng)曝光時(shí)間上限(us)
*/
private BigDecimal autoExposureTimeUpperLimit;
/**
* 采集幀率
*/
private BigDecimal acquisitionFrameRate;
/**
* 增益自動(dòng)開關(guān)(us)
*/
private String gainAuto;
/**
* 增益自動(dòng)上限
*/
private BigDecimal gainAutoUpperLimit;
/**
* 增益自動(dòng)上限
*/
private BigDecimal gainAutoLowerLimit;
}
}
3. 調(diào)用SDK的服務(wù)方法查詢
PinkDeviceCameraConfigRequest pinkDeviceCameraConfigRequest = new PinkDeviceCameraConfigRequest();
pinkDeviceCameraConfigRequest.setConfigType("DEVICE_NO");
pinkDeviceCameraConfigRequest.setDeviceNo("123@LuSun");
//單個(gè)查詢場(chǎng)景
PinkDeviceCameraConfigResponse response =
paramInstQueryService.getParams("P80-DEVICE-CAMERA-PARAM-MANAGER",
pinkDeviceCameraConfigRequest,
PinkDeviceCameraConfigResponse.class);
//批量查詢場(chǎng)景
PageQueryOption pageQueryOption = new PageQueryOption();
pageQueryOption.setPageIndex(1);
pageQueryOption.setPageSize(200);
PageInfo<PinkDeviceCameraConfigResponse> paramsPage =
paramInstQueryService.getParamsPage("P80-DEVICE-CAMERA-PARAM-MANAGER",
pinkDeviceCameraConfigRequest,
PinkDeviceCameraConfigResponse.class,
pageQueryOption);
4. 獲得結(jié)果
整體查詢實(shí)現(xiàn)原理如下:
目前整個(gè)服務(wù)的性能在10+ms左右:
參數(shù)優(yōu)先級(jí)實(shí)現(xiàn)
為什么會(huì)有參數(shù)優(yōu)先級(jí)這個(gè)功能?
比如有一個(gè)場(chǎng)景,要維護(hù)一個(gè)供應(yīng)鏈系統(tǒng)中的補(bǔ)貨參數(shù):安全庫(kù)存,低于這個(gè)安全庫(kù)存的時(shí)候,要通知商家進(jìn)行補(bǔ)貨,整個(gè)供應(yīng)鏈里有100個(gè)倉(cāng)庫(kù),20個(gè)一級(jí)類目,200個(gè)二級(jí)類目,2000個(gè)三級(jí)類目,涉及到500個(gè)品牌,要維護(hù)每一個(gè)商品的安全庫(kù)存,你會(huì)怎么實(shí)現(xiàn)?
你一定不會(huì)把 100倉(cāng)庫(kù)*2000類目*500品牌 = 1000000000種可能全都設(shè)置一遍參數(shù),對(duì)你來(lái)說(shuō),重點(diǎn)類目,要單獨(dú)詳細(xì)配置安全庫(kù)存,非重點(diǎn)類目可能只需要管控到一級(jí)或者二級(jí)類目即可,這樣你所需要的配置會(huì)大大減少。那么參數(shù)的決策就需要遵循一定的規(guī)則,比如:
有倉(cāng)庫(kù)+一級(jí)類目+二級(jí)類目+三級(jí)類目 的安全庫(kù)存,優(yōu)先?。?/span>
如果取不到,則取倉(cāng)庫(kù)+一級(jí)類目+二級(jí)類目的安全庫(kù)存;
再取不到,取倉(cāng)庫(kù)+一級(jí)類目的安全庫(kù)存。
比如:
DN倉(cāng) 鞋 安全庫(kù)存 100
DN倉(cāng) 鞋-運(yùn)動(dòng)鞋 安全庫(kù)存 500
DN倉(cāng) 鞋-運(yùn)動(dòng)鞋-籃球鞋 安全庫(kù)存 1000
那如果一個(gè)商品是籃球鞋的話,則會(huì)命中安全庫(kù)存1000的規(guī)則,如果是登山鞋的話,只能命中運(yùn)動(dòng)鞋的規(guī)則取500,如果是高跟鞋,則只能取100的安全庫(kù)存。
(事實(shí)上這種補(bǔ)貨規(guī)則要詳細(xì)的多,這里只是方便大家理解需求,并不是真正的參數(shù))
也就是說(shuō),當(dāng)用戶的入?yún)⑼瑫r(shí)可能命中多條參數(shù)的時(shí)候,需要通過優(yōu)先級(jí)來(lái)判斷應(yīng)該返回哪個(gè)參數(shù)。
為了加速查詢,系統(tǒng)在設(shè)計(jì)時(shí)添加了兩層緩存:
當(dāng)后臺(tái)數(shù)據(jù)發(fā)生變化時(shí),會(huì)將對(duì)應(yīng)的緩存進(jìn)行失效。
元素多選處理
維度多選場(chǎng)景:
參數(shù)多選場(chǎng)景:
既要保證維度唯一,又要保證能正常搜索,以及展示,如何實(shí)現(xiàn)?業(yè)務(wù)參數(shù)配置中心引入了一個(gè)“組”的概念,是將同屬于一行的參數(shù)實(shí)例,歸為一個(gè)組,這個(gè)組是最小的新建、編輯單位。
對(duì)于新增流程如下圖所示:
對(duì)于修改流程,如下圖所示:
元素范圍查詢
頁(yè)面中的字段,我們統(tǒng)稱為元素,只要是字段,一定有它的取值范圍,我們平衡了用戶使用成本以及系統(tǒng)性能,將字段取值類型劃分成了四種:
1)枚舉類元素
2)dubbo全量接口元素
3)dubbo單點(diǎn)查詢接口元素
4)自定義文本元素
1. 枚舉元素由用戶手動(dòng)在頁(yè)面創(chuàng)建,一般幾十個(gè)以內(nèi)為佳,創(chuàng)建成本不高,比如經(jīng)常用到的 “是”,“否”,或者比如單據(jù)類型等等。
2. dubbo全量接口元素,一般是幾十到上百個(gè)的體量,比如一級(jí)類目,倉(cāng)庫(kù)等,地址。
3. dubbo單點(diǎn)查詢接口,一般是幾千到幾萬(wàn)體量的取值范圍,無(wú)法直接在內(nèi)存里存儲(chǔ)所有枚舉,比如品牌等。只能通過兩個(gè)接口來(lái)完成搜索以及數(shù)據(jù)的展示,比如“品牌ID >品牌名稱”接口 和 “品牌名稱->品牌ID” 接口。
4. 自定義文本,非枚舉類字段,可以選擇使用自定義文本來(lái)承接。
比如以下是可以通過dubbo接口全量獲取配置的元素:
與dubbo全量接口的錄入類似,單點(diǎn)搜索接口與全量接口不同的點(diǎn)在于,單點(diǎn)接口需要保留一個(gè)變量,給系統(tǒng)查詢時(shí)調(diào)用,比如“通過品牌ID 查詢品牌名稱” 和 “通過品牌名稱查詢品牌ID” ,需要留給系統(tǒng)調(diào)用的入?yún)ⅲ?{var}代替。
當(dāng)然,有時(shí)元素的范圍并不是只取決于它自己,可能也取決于同頁(yè)面里其他元素的取值,比如說(shuō)有一個(gè)質(zhì)量原因的字段,當(dāng)一級(jí)類目為鞋時(shí) 取值為A、B、C,為服裝時(shí)為 D、E、F,這是元素范圍在設(shè)置時(shí),就需要將對(duì)應(yīng)的元素入?yún)⒕S護(hù)到其中,比如:
接口入?yún)㈩愋?/span> | 接口入?yún)⑷≈?/span> |
com.d.s.q.s.d.r.ConfigRequest | {"ruleVersion":#{ruleVersion},"spuId":#{spuId}} |
導(dǎo)入導(dǎo)出
以下是導(dǎo)入處理流程:
為了照顧使用人員的體驗(yàn),再多數(shù)導(dǎo)入場(chǎng)景時(shí),我們的導(dǎo)入文件都用的是文案,而不是后臺(tái)存儲(chǔ)的數(shù)值,比如導(dǎo)入的字段包含類目時(shí),導(dǎo)入文件輸入的是鞋、服裝、美妝等文案,而不是2、3、4這樣存儲(chǔ)在后臺(tái)的數(shù)值,那么勢(shì)必這里就會(huì)有將文案轉(zhuǎn)換成數(shù)值的過程,這其中就用到了2.3.5章節(jié)中提到的元素范圍查詢使用的接口,當(dāng)然,對(duì)于需要其他元素作為入?yún)⒌脑?,我們默認(rèn)每個(gè)元素左邊的元素都可以作為當(dāng)前元素的入?yún)ⅰ?/span>
業(yè)務(wù)參數(shù)配置中心不適合做什么?
1. 有極為復(fù)雜的UI交互
2. 較為復(fù)雜的校驗(yàn)邏輯(長(zhǎng)期計(jì)劃支持)
3. 高頻寫入場(chǎng)景
4. 應(yīng)用查詢參數(shù)時(shí)以非“=”條件匹配
三、總結(jié)與展望
本文簡(jiǎn)要描述了業(yè)務(wù)參數(shù)配置中心的設(shè)計(jì)思路,參數(shù)配置中心配套生成增、刪、改、查、導(dǎo)入、導(dǎo)出服務(wù),并且結(jié)合前端低代碼平臺(tái)自動(dòng)生成前端代碼,平臺(tái)目前業(yè)務(wù)參數(shù)中心已經(jīng)有40+個(gè)場(chǎng)景接入節(jié)省了大量的工作人日,能夠讓研發(fā)人員,擺脫低效的CRUD,更專注于自己內(nèi)部業(yè)務(wù)邏輯的開發(fā)。
對(duì)于目前系統(tǒng)的未來(lái)規(guī)劃:
1. 持續(xù)增加SDK的查詢靈活性:包括不限于批量代參數(shù)優(yōu)先級(jí)對(duì)數(shù)據(jù)進(jìn)行查詢、通過SDK分頁(yè)查詢?nèi)繀?shù)、對(duì)系統(tǒng)字段吐出方便業(yè)務(wù)方使用;
2. 持續(xù)增加對(duì)方案定義的靈活性:支持更多的元素范圍的定義,比如HTTP等調(diào)用方式;
3. 持續(xù)增加對(duì)元數(shù)據(jù)定義的靈活性:部分元數(shù)據(jù)的取值可能需要同頁(yè)面中的另一個(gè)元素的取值來(lái)決定,所以在取值渲染時(shí),可以保留給其他元素的占位符,進(jìn)而隨著頁(yè)面的動(dòng)態(tài)變動(dòng),后臺(tái)取值也可以動(dòng)態(tài)變動(dòng)。