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