1.1 背景

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

1.2 寻衅

有赞php面试有赞订单导出的设置装备摆设化实践 Webpack

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

订单导出须要超过来自不同行业、不同产品、不同模块,对各个业务域的存储和设计有整体理解;同时,须要通过技能手段(数据域、存储域、报表域、文件域)聚合来自各个域的数据凑集,天生可读的报表下载给商家。

由此可见,其紧张的寻衅是:如何快速支持各个域灵巧多变的导出字段需求。
如何应对这一寻衅呢?

二、 架构重构

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

在小量订单导出的环境尚能搪塞,一旦同时有多个数万订单导出任务时,资源占用非常大,CPU 基本被打满,PHP 导出进程被壅塞,从而壅塞了所有的订单导出,导出就无法供应做事了。
直接访问业务数据库存在一种潜在风险:如果访问业务数据库的数据量很大,SQL 编写不当导致慢查,每每会给业务数据库带来访问压力,严重影响正常核心业务流程。

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

重构之后,订单导出的性能和稳定性有了很大的提升:

支持百万级订单的导出,且导出的速率比之前大幅提升。
以前导出几万订单慢且易壅塞,现在均匀能导出 1w/1min,大多数导出可在十几秒内完成。
ES 和 Hbase 具有天然的弹性和容量扩展性,纵然总订单量有数量级的增长,导出的速率和稳定性也不受影响。
摆脱了随意马虎被壅塞的困境,不再直接访问业务数据库,关闭了导出对核心业务流程的潜在威胁。

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

三、配置之旅

3.1 初尝配置:设下伏笔

订单导出常常要面临添加新的报表字段的需求。
最初实现不太灵巧,是来一个字段,在代码流程里添加一个字段。
每次增加新的字段,都须要修正多处。
因此,第一个优化是采取函数接口编程,将字段定义做成列举可配置化的,然后遍历指定的报表字段列表,拿到对应的字段定义,打算字段的值,写入报表文件。

根据报表字段列表天生报表行的伪代码如下:

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: 在报表配置中引用该字段的标识。

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

整体流程打通后,当须要新增个性化字段时,常日只要做两步:

增加个性化字段的配置,包括 Groovy 脚本;测试通过后,刷新运用的配置即可。

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

3.4 通用导出:锦上添花

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

终极采取的办理方案是:对分销采购单的导出需求和所需技能进行抽象,实现一个更加通用的导出能力模型,支持交易领域的各种潜在导出需求,而不仅仅局限于分销采购单导出。
通过剖析订单导出流程可以创造,绝大多数导出都遵照如下核心流程:

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

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

四、质量保障

前面提到,订单导出的报表字段非常多,导出数据量大,如何担保代码改动或重构后订单导出的做事质量和数据准确性?紧张手段如下:

质量流程保障是第一位的。
最紧张的三项是:单测严格全部通过; CodeReview 由运用任务人及履历丰富的高等工程师同时通过;预发线上导出比拟工具通过。
整体架构设计担保了订单导出的性能、稳定性和可扩展性。
持续小幅重构使得系统能够持续优化,避免一次性大改造伤筋动骨且随意马虎导致线上故障。
设计先行,对代码质量非常重视。
运行预发线上订单导出自动化比拟工具,很大程度上增强了成功发布的信心,是发布前保障质量的一道主要防线。

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

关注作者私信,关键词:“ 架构 ”

可免费获取一份Java架构进阶技能佳构视频。
(高并发+Spring源码+JVM事理解析+分布式架构+微做事架构+多线程并发事理+BATJ口试宝典)以及架构思维导图。

如果以为文章还不错的话。

记得:转发 转发 转发 !