本文由做者王改革受权网易云社区发布。前端
1、背景和实现目标后端
在开发严选数据产品(大麦商品数据运营平台和移动数据工做台VIPAPP)的时候,最多的业务场景就是对实时和离线数据模型中查询、处理、统一数据结构返回给前端。因此在开发的同时也一直在思考如何将这些类似的数据处理流程统一块儿来,更关注数据指标自己。缓存
开发中常常遇到的几个问题是:数据结构
数据查询链接管理分散 并发
模型查询结果缓存分散 框架
对于模型数据查询结果缺乏统一的数据变换模块支持,每日产出的实时数据指标以及离线数据指标通过后端逻辑作接口返回的时候,会有大量的get、set操做,若是同时须要计算指标同比、环比、占比、对比值等复合指标时,就会充斥大量的重复脏代码。spa
依赖的数据服务对存储在MySQL、GP、Kylin、HBase等存储引擎的数据模型暂时没有多模型的链接支持。线程
因此针对以上问题,咱们但愿可以设计出可以在数据产品中使用的通用指标查询计算模块(DPRequestManager),主要实现以下目标:设计
管理数据模型查询,封装对于 统一查询服务(DQS)、MySQL等查询请求,提供查询链接池。excel
提供灵活的数据变换能力
可以经过配置对相应指标(包括指标值、环比、占比、同比等)自动计算,减小过多的冗余代码。
支持数据对象映射,减小频繁的取值和赋值操做
支持查询级别的缓存(能够根据系统需求自定义缓存时间、能够设定缓存条件),减小对依赖服务的查询压力。
2、通用指标查询计算模块(DPRequestManager)组织结构
红色部分的并发查询器负责管理数据产品与底层数据查询存储引擎的链接,统一管理各类数据查询。DPRequestManager会做为数据产品模型查询的统一入口,封装底层数据模型存储引擎,为数据查询提供线程池服务。同时此部分还提供模型退化的能力。
统一缓存模块负责对数据查询作条件缓存,对于每日数据未产出或者其余状况能够对查询结果约定一些必要条件来决定是否缓存查询结果(缓存级别为请求级别)。
橙色部分的数据变换模块能够对DQS获取的多表数据进行灵活数据变换
配置模块能够对数据关系映射以及相应的复合指标计算作相应配置,统一辈子成结果,减小冗余代码
3、数据变换模块支持操做
对于从模型存储引擎查询结果处理成List<Map<String, Object>>结构的,并提供提供如下数据变换操做。
命名规则:模块内部将使用数据dataKey存放查询处理结果,dateKey对应的list结果能够类比excel行列表,其中的列名对应Map结构的key值。
如今主要提供以下数据操做:
compose({"group_id", "group_name"}, "result_A", "resultB")
rename ("dk", {"sale_amount_day", "sale_qty_day"}, {"sale_amount, sale_qty"})
group(row -> new StringJoiner("_").add(row.get("week").add(row.get("group_id")))
aggregation({"week", "group_id", "group_name"}, {sum("sale_amount"), sum("sale_qty")})
4、指标计算配置以及对象关系映射
这个地方主要解决两个问题:
模型数据查询结果到数据对象的映射,减小频繁的取值和赋值,如今通用的ORM框架都能解决这个问题,DPRequestManager为了配合复合指标计算经过反射来实现数据对象映射部分。
映射过程当中的指标计算(指标表达式计算、环比、同比、占比等复合指标的计算)
DPRequestManager主要是经过两个配置来配合解决复合指标计算的问题。
第一个配置主要是定义数据对象映射的数据筛选规则(目标数据集合、环比数据集合、占比数据集合等)
{
"clazz": StockDTO.class, "useDate": "2018-12-07", // 指定目标值日期 "hbDate": "2018-12-06", // 指定环比日期 "hbKey": "group_id", // 计算环比使用 "filterKey": "", // 过滤器 "filterValue": ""
}
第二个配置是数据对象DTO的配置(经过注解对复合指标计算进行配置定义)
DTO配置主要使用了三个注解,@FromDO,@FunctionDO,@IgnoreAssign
@From 定义简单按key取值
@FunctionDO定义复合指标计算规则(hb,tb,zb,avg,sum等)
@IgnoreAssign 对象映射是字段忽略
同时支持定义的复合指标对象的指标计算赋值。
@Datapublic class StockSingleVO extends BaseVO { // 简单取值,默认驼峰转下划线取字段group_id
private Long groupId; @FromDO("'商品组:'+group_name") private String groupName; // 简单取值,直接在库量 @FromDO("stock_cnt_zhuzhan_1d") private Number stockCnt; // 环比,对在库量字段计算环比 @FromDO("stock_cnt_zhuzhan_1d") @FunctionDO(types = FunctionTypeEnum.HB) private Number stockCntHB; // 简单取值,计算在库+在途量 @FromDO(value = "stock_cnt_zhuzhan_1d+onway_cnt_zhuzhan_1d") private Number stockAndOnwayCnt; // 环比计算,计算在库+在途的环比 @FromDO(value = "stock_cnt_zhuzhan_1d+onway_cnt_zhuzhan_1d") @FunctionDO(types = FunctionTypeEnum.HB) private Number stockAndOnwayCntHB; // 占比计算 @FromDO(value = "stock_cnt_zhuzhan_1d+onway_cnt_zhuzhan_1d") @FunctionDO(types = FunctionTypeEnum.ZB) private Number stockAndOnwayCntZB;
}
数据过滤规则配置和数据对象中定义的复合指标计算配置一块儿支持数据对象映射,这样能够减小大量重复赋值取值以及手动计算复合指标的工做。同时配合使用数据变换模块和数据对象映射可以释放更大的灵活性,将数据变换模块、数据对象映射、复合指标计算模块解耦。
模块查询代码示例:
/**
*/EngineRequest request1 = new EngineRequest(EngineType.DQS, condition1, EngineResultTypeEnum.LIST); // 构造查询请求1EngineRequest request2 = new EngineRequest(EngineType.DQS, condition2, EngineResultTypeEnum.LIST); // 构造查询请求2// 构造配置1(数据过滤配置 -> 配置目标日期、环比日期、计算环比目标分组key)EngineConvertConfig config = new EngineConvertConfig<>(StockSingleVO.class, "2018-12-07", "group_id", null, null);
config.setHbDate("2018-12-06");
Listlist = requestEngineManager.initThreadLocal()
.addRequest(dk1, request1) .addRequest(dk2, request2) .execute() .compose(true, Arrays.asList("date_id", "group_id"), dk_new, dk1, dk2) // 请求结果链接 .converToList(dk_new, config); // 数据对象映射// 前端数据结构组装SmartQueryResult result = BaseVO.assembleResult(list, StockSingleVO.class);
5、总结
数据产品中不少通用的部分能够抽出来做为单独模块或者服务。文中介绍的复合指标查询模块已经在大麦商品数据运营平台中实践,它把数据产品指标查询、计算以及对象映射等公共部分提取出来,有效的提升开发效率并可以下降开发成本。
文章来源: 网易云社区