得物业务参数配置中心架构综述

一、背景

现状与痛点

在目前互联网飞速发展的今天,企业对用人的要求越来越高,尤其是后端的开发同学大部分精力都要投入在对复杂需求的处理,以及代码架构,稳定性的工作中,在对比下,简单且重复的CRUD就显得更加浪费开发资源。目前scm供应链管理页面中,存在约77%的标准页面,这些标准页面里,还存在着很多类似的参数配置页面,就是对某一个模型进行增、删、改、查、导入、导出进行类似的操作,这种开发工作技术含量较低,而且相对耗费人力。

什么是业务参数配置中心

参数配置中心,是一个能够通过配置的方式,快速生成前端页面以及配套增、删、改、查、导入、导出服务的配置平台,它与得物内部低代码前端页面平台wizard相互集成,参数配置中心提供后台增删改查服务,wizard输出对应的前端页面代码,并可以支持用户自定义修改。

使用场景

  • 针对读多写少的简单的单表的增删改查;
  • 业务中需要交给运营来修改的复杂ark配置(简单配置除外),可以尝试使用业务参数配置中心接入,减少人为修改JSON可能产生的错误,导致系统无法编译进而产生故障。

比如如下的JSON:

css 复制代码
[{"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:
arduino 复制代码
@Data

public class PinkDeviceCameraConfigRequest  implements Serializable  {
  

    */***

     * 配置类型

     */

    private String configType;

    */***

     * 设备编号

     */

    private String deviceNo;


}
  1. 定义response
arduino 复制代码
@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;

    }
}
  1. 调用SDK的服务方法查询
ini 复制代码
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);
  1. 获得结果

整体查询实现原理如下:

目前整个服务的性能在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. 持续增加对元数据定义的灵活性:部分元数据的取值可能需要同页面中的另一个元素的取值来决定,所以在取值渲染时,可以保留给其他元素的占位符,进而随着页面的动态变动,后台取值也可以动态变动。

往期回顾

  1. 得物增长兑换商城的构架演进

  2. 得物自研DGraph4.0推荐核心引擎升级之路

  3. 大语言模型的训练后量化算法综述 | 得物技术

  4. 如何合理规划Elasticsearch的索引|得物技术

  5. DPP推荐引擎架构升级演进之路|得物技术

文 / sakuta

关注得物技术,每周新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

相关推荐
weifont35 分钟前
聊一聊Electron中Chromium多进程架构
javascript·架构·electron
热河暖男1 小时前
【实战解决方案】Spring Boot+Redisson构建高并发Excel导出服务,彻底解决系统阻塞难题
spring boot·后端·excel
国际云,接待4 小时前
云服务器的运用自如
服务器·架构·云计算·腾讯云·量子计算
noravinsc5 小时前
redis是内存级缓存吗
后端·python·django
noravinsc6 小时前
django中用 InforSuite RDS 替代memcache
后端·python·django
好吃的肘子7 小时前
Elasticsearch架构原理
开发语言·算法·elasticsearch·架构·jenkins
喝醉的小喵7 小时前
【mysql】并发 Insert 的死锁问题 第二弹
数据库·后端·mysql·死锁
编程星空7 小时前
架构与UML4+1视图
架构
kaixin_learn_qt_ing7 小时前
Golang
开发语言·后端·golang
炒空心菜菜8 小时前
MapReduce 实现 WordCount
java·开发语言·ide·后端·spark·eclipse·mapreduce