有赞订单导出的配置化实践

1、引子

1.1 背景

有赞订单导出业务隶属于有赞交易订单管理组,主要职能是将有赞商家的订单数据经过报表的形式导出并提供下载给商家使用。目前承接了有赞全部的订单导出业务,报表的字段覆盖交易、支付、会员、优惠、发货、退款、特定业务等,合计超过 100 个。java

1.2 挑战

随着有赞的迅速发展,有赞的行业、业务与产品覆盖面愈来愈广。从行业角度来看,覆盖了微商城、新零售、餐饮、美业、教育等,从模块角度来看,覆盖了交易、资产、客户、营销、店铺等,从产品角度来看,覆盖了分销、精选等。每一个行业、模块、产品都会在订单导出报表中有所诉求。以下图所示,展现了有赞订单导出的域模型:数据库

图片描述

订单导出须要跨越来自不一样行业、不一样产品、不一样模块,对各个业务域的存储和设计有总体理解;同时,须要经过技术手段(数据域、存储域、报表域、文件域)聚合来自各个域的数据集合,生成可读的报表下载给商家。编程

因而可知,其主要的挑战是:如何快速支持各个域灵活多变的导出字段需求。如何应对这一挑战呢?设计模式

2、 架构重构

订单导出的最初实现是从交易的多个 DB 及多个业务 API,分别获取交易、支付、会员、发货、退款、核销、分销等多个数据,组装到一块儿生成报表。采用 PHP 任务脚原本实现。这种作法有两个痛点:缓存

  • 在小量订单导出的情形尚能应付,一旦同时有多个数万订单导出任务时,资源占用很是大,CPU 基本被打满,PHP 导出进程被阻塞,从而阻塞了全部的订单导出,导出就没法提供服务了。
  • 直接访问业务数据库存在一种潜在风险:若是访问业务数据库的数据量很大,SQL 编写不当致使慢查,每每会给业务数据库带来访问压力,严重影响正常核心业务流程。

基于这两个痛点,有赞订单管理组进行了架构升级,详见有赞技术博文《有赞订单管理的三生三世与“十面埋伏》。 得益于此,订单导出也迁移到基于 ES + Hbase 的技术栈。其中订单搜索采用 ES 服务实现,订单详情则存储在 Hbase 中,经过 API 来获取。总体流程以下所示:架构

图片描述

重构以后,订单导出的性能和稳定性有了很大的提高:并发

  • 支持百万级订单的导出,且导出的速度比以前大幅提高。之前导出几万订单慢且易阻塞,如今平均能导出 1w/1min,大多数导出可在十几秒内完成。
  • ES 和 Hbase 具备自然的弹性和容量扩展性,即便总订单量有数量级的增加,导出的速度和稳定性也不受影响。
  • 摆脱了容易被阻塞的困境,再也不直接访问业务数据库,关闭了导出对核心业务流程的潜在威胁。

接下来,开始了配置化之旅。函数

3、配置之旅

3.1 初尝配置:设下伏笔

订单导出经常要面临添加新的报表字段的需求。最初实现不太灵活,是来一个字段,在代码流程里添加一个字段。每次增长新的字段,都须要修改多处。所以,第一个优化是采用函数接口编程,将字段定义作成枚举可配置化的,而后遍历指定的报表字段列表,拿到对应的字段定义,计算字段的值,写入报表文件。 工具

根据报表字段列表生成报表行的伪代码以下:post

public List<String> generateReportLineData(List<String> fields) {
        return StreamUtil.map(fields, field -> {
            try {
                FieldDefinition fieldDef = getFieldDefinition(field);
                FieldMethod method = getMethod(fieldDef);
                String value = method.invoke(this.reportItem);
                return postproc(value);
            }catch (Exception e){
                logger.warn("failed to get value for field: {} orderNo: {}", field, reportItem.getOrderNo());
                return "";
            }
        });
    }

这个小小的优化,为进一步的配置化设下伏笔。当须要新增报表字段时,只要增长新的字段定义,而不须要在流程里增长代码。加强软件可扩展性的一个重要方法是,将流程变得通用,只要增删流程里的环节及定义便可。

凡基础必要老是正确的方向。

3.2 报表配置:破局之时

有赞新零售、餐饮的迅速兴起和发展,须要低成本快速地搭建起零售和餐饮的订单导出。这要求订单导出具备更大的灵活性,可以根据不一样行业的要求配置不一样的字段列表及导出格式,同时又能互不影响。此外,不一样商家有个性化的导出需求。然而,原来的订单导出,是专门为微商城开发的商品级别的报表。要加一个字段,每每会影响全部的有赞商家,使用体验不佳,订单报表自己也变得臃肿不堪。

如何突破原来的局限,支持更灵活的订单导出呢?这是订单导出面临的一个破局点。经过订单导出模板解决了这个问题。针对行业、产品配置的导出模板存储在 DB 表 export_biz_conf 里;针对有赞商家的导出模板存储在 DB 表 export_customized_conf 里。每一个导出模板包括了以下信息:报表字段列表、导出维度(订单及商品)、报表文件格式、可选项等,作到足够灵活。

若要导出不一样报表字段,只要新增相应字段,指定报表字段列表便可;若要生成不一样维度的报表,可以使用策略模式。好比,

  • 导出大订单量,采用批量并发策略更高效;导出小订单量,采用串行策略更易理解;
  • 能够把字段定义写在本地代码里直接引用,或者配置在 Groovy 脚本里更加灵活;
  • 能够根据指定的订单级别或商品级别进行维度聚合,而后计算报表字段的值;
  • 能够根据指定的 csv 或 excel 生成相应的文件。

如图所示: 针对导出流程的各环节,可采用策略模式来选择不一样实现,而后将策略组合起来。

图片描述

经过实现报表配置功能,突破了以前的局限,能够支持不一样行业、产品的标准化和定制化导出需求,而且作到相互隔离不干扰。

3.3 配置深化:更快更稳

随着有赞进入更多的行业,面临着更加多变和个性化的导出需求。好比,有赞教育须要导出知识订单的学员信息和课程信息,有赞零售须要导出导购员和发货仓库门店名称。 显然,若是要完成某个导出需求,还须要修改代码、发布系统,这种操做会很是频繁,致使开发和维护成本提高,影响系统稳定性。

若是可以在应用运行中动态地新增报表字段并加载和使用,无需修改导出工程代码,无需从新发布系统,就能更加快速地支持导出需求,将会大幅下降导出需求支持的开发和维护成本,保持系统稳定性。

为了解决这个问题,引入了动态脚本语言 Groovy. Groovy 是可以与 Java 无缝对接的好伙伴,能够直接使用 Java 类的功能。编写 Groovy 脚本实现报表字段逻辑,存储在字段配置表 export_field_conf 里, 在报表配置表 export_biz_conf 或 export_customized_conf 里引用,而后在应用启动时缓存到内存里并使用。好比粉丝姓名的 Groovy 脚本以下:

import com.youzan.trade.orderexport.util.PublicUtil 

def fansInfo = reportItem.orderInfo.extra["FANS"]
PublicUtil.fetch(fansInfo, "nickname")

PublicUtil 是导出工程里封装的一个工具类,可让编写字段配置脚本更加简单。值得说起的是,为了不使用 Groovy 脚本可能致使的内存泄露,须要对编译后的 Groovy 脚本进行缓存和执行。

为了实现无需改动代码和发布系统,还须要在总体流程上打通。总体流程以下:

Step1: 当用户下单后,源数据落到业务数据库的扩展信息里;
Step2: 经过数据同步,自动同步到 Hbase 表;
Step3: 经过 Apollo 配置和可扩展的数据聚合机制,将数据自动输送到用来计算报表字段值的报表对象里;
Step4: 新增报表字段的配置;
Step5: 在报表配置中引用该字段的标识。

下图展现了经过配置自定义字段快速支持导出需求的总体流程。

图片描述

总体流程打通后,当须要新增个性化字段时,一般只要作两步:

  1. 增长个性化字段的配置,包括 Groovy 脚本;
  2. 测试经过后,刷新应用的配置便可。

个性化字段配置能力已经在线上稳定运行,好比拼团订单成团时间、零售导购员、有赞教育的课程字段等。

3.4 通用导出:锦上添花

紧接着,订单导出又面临分销采购单的导出需求。分销采购单导出流程跟订单导出有所不一样,须要分别导出分销买家订单和供货订单的详情信息再导出。这个流程跟通用的订单导出流程是有所区别的。若是经过修改订单导出的通用流程来支持,显然会影响全部的订单导出,使订单导出流程不清晰。

最终采用的解决方案是:对分销采购单的导出需求和所需技术进行抽象,实现一个更加通用的导出能力模型,支持交易领域的各类潜在导出需求,而不只仅局限于分销采购单导出。经过分析订单导出流程能够发现,绝大多数导出都遵循以下核心流程:

图片描述

能够将核心流程作成插件式的。首先,定义一个插件接口,包含其配置和功能等;其次,实现经常使用的插件列表,支持从 ES, HBase, API 查询或获取数据,以及经常使用的过滤、排序、格式化、生成报表等功能;最后,将这些插件列表串联成一个具体的导出实例。总体流程则采用模板方法模式复用了订单导出流程。

好比微商城分销采购单导出经过依次执行ES查询插件、订单详情插件、数据排序插件、报表字段格式化插件、报表生成插件来实现,其中订单详情插件针对分销买家单和供货订单分别调用了一次。

4、质量保障

前面提到,订单导出的报表字段很是多,导出数据量大,如何保证代码改动或重构后订单导出的服务质量和数据准确性?主要手段以下:

图片描述

  • 质量流程保障是第一位的。最主要的三项是:单测严格所有经过; CodeReview 由应用责任人及经验丰富的高级工程师同时经过;预发线上导出对比工具经过。
  • 总体架构设计保证了订单导出的性能、稳定性和可扩展性。
  • 持续小幅重构使得系统可以持续优化,避免一次性大改造伤筋动骨且容易致使线上故障。
  • 设计先行,对代码质量很是重视。
  • 运行预发线上订单导出自动化对比工具,很大程度上加强了成功发布的信心,是发布前保障质量的一道重要防线。

此外,采用函数编程及设计模式,使代码实现层面更具复用性和柔软性。18K 行代码,代码重复率约为 1.8%。

5、小结与致谢

本文简要讲述了有赞订单导出的配置化实践。经过配置化以后,订单导出的能力和稳定性有了大幅提高。固然,还有一些须要提高的地方。好比,能够增长扩展点机制,容许业务方定制化导出;局部细节能够打磨得更细腻。欢迎对海量订单业务感兴趣有经验的小伙伴与咱们一块儿共建订单管理大局!简历可直邮 shuqin@youzan.com.

在这个过程当中,有许多小伙伴给予了有力的支持,好比产品同窗对订单报表的细致的规划设计、客满运营同窗提出的及时反馈、有赞技术团队的支持以及本身的付出。

相关文章
相关标签/搜索