取消

发表评论

登录/注册 后参与评论
取消 确定

网易严选仓配调度服务平台化实践之路

华斌 ·2020/1/20
摘要: 为解决其中的痛点,网易严选仓配调度服务平台化技术改造要解决六个核心问题,一是重复建设问题、二是平台扩展性问题,三是业务耦合问题,四是业务流程编排问题,五是策略评价和优化问题,六是策略追溯。


仓配调度服务简介

网易严选作为新兴品牌电商,为尽快把商品送到客户手上,提升用户体验,在全国设有多个仓库(多个自营系统仓 + 多个外部合作仓),同时对接了多家快递配送商。严选仓配调度服务的核心功能则是为严选订单寻找最佳仓配履约路径,即为一个订单选择在哪个仓库生产,选择由哪个快递配送商来配送。

仓配调度服务目前对接了十余个业务产品,主要分两类,一是线上各种渠道的订单的仓配调度,二是线下的各种策略模拟,服务的全局视图如下。


为什么要平台化技术改造

服务的演进过程如下图,最初只对接了单一业务场景,但随着严选业务的发展,逐步承载了线上线下十余个业务场景的仓配决策逻辑,且为避免线上线下逻辑互相影响,建设了两套代码。

它们是烟囱式架构和单体臃肿代码的结合体。


烟囱式架构

为线上线下业务场景建设了两套代码,即两个工程,好处是可以完全隔离,避免互相影响,但最大问题是重复建设,很大一部分逻辑和代码是相同的,增加一个功能常常要在两套代码上都写一遍。


单体臃肿代码

而在工程代码内部,则表现为单体臃肿代码。系统对接了很多业务场景,各业务场景有共性也有独特的逻辑。在早期业务野蛮发展阶段,为了将业务快速落地,常常一个工程多人开发且没时间做抽象,使得代码缺乏结构性,模块渐进模糊,导致共性部分常常是重复逻辑重复代码,越来越有烟囱式代码的味道,而独特部分则是逻辑混杂一起的众多大块的if esle。


痛点

烟囱式架构和单体臃肿代码导致如下痛点:

  1. 由于代码臃肿和重复代码越来越多,致使开发效率越来越低,新人不容易上手;

  2. 由于各业务场景逻辑耦合,互相影响,每次改动可能牵一发动全身,系统稳定性变差,每次需求完成后QA全局回归的代价较高;

  3. 如果要理清楚其中一个业务场景的逻辑,需要遍历全局代码。

如果继续这样粗放的建设下去,我们对业务的响应速度会越来越慢,痛点也将更加突出。为了能更快速响应业务需求的变化并保持系统稳定,我们要将烟囱式架构和单体臃肿代码进行平台化技术改造,用平台来统一管理各个业务场景的调度流程。


仓配调度服务的诉求

除了要快速响应业务需求的变化并保持系统稳定,调度服务自身还有如下诉求:

  1. 业务调度策略灵活变化,平台需要提供各种策略组件供各业务自由组装编排,并提供策略评价和策略优化的指导;

  2. 改变“调度策略变更即是去除老代码、增添新代码”的传统开发方式,这种方式不方便历史调度策略的追溯,平台需要将策略管理起来,以方便追溯和复盘。


平台化技术改造的目标

上节分析了系统的现状和诉求,为解决其中的痛点,我们平台化技术改造要解决六个核心问题,一是重复建设问题、二是平台扩展性问题,三是业务耦合问题,四是业务流程编排问题,五是策略评价和优化问题,六是策略追溯。为此,系统在技术上至少需提供如下特性:

  1. 复用性 --解决重复代码和重复能力建设问题,新业务的对接最大程度复用已有能力和已有代码。

  2. 扩展性 -- 解决不同业务流程的个性化扩展问题,平台提供相应扩展机制方便业务扩展。

  3. 隔离性 -- 解决不同业务流程的耦合问题,各业务流程尽可能避免互相影响导致系统不稳定。

  4. 配置化流程编排 -- 解决在平台上快速构建业务流程的问题,灵活组装模块和数据,方便配置出各种业务流程,并支持业务流程运行时模块替换。

  5. 策略闭环 -- 仓配调度服务属于供应链中的一个策略系统,如果某业务场景要变更其策略时,需提供线下的模拟和线上的灰度,结合策略效果指标计算和比对功能,以反馈指导策略选择和优化。

  6. 可追溯性 -- 业务流程和策略存储和管理,并用唯一ID标识,方便路由和历史追溯

这些特性在价值层面则是提高研发效率、提升系统稳定性和数据赋能


平台化思路

为实现上述目标,我们的平台化技术改造总体落地思路如下:

  1. 统一调度模型,抽象出通用的仓配调度业务流程;

  2. 预留扩展点以支持平台可扩展;

  3. 模块化开发;

  4. 业务流程编排;

  5. 业务接入和身份路由。


梳理、提炼和抽象出通用的仓配调度业务流程

梳理现有的各业务代码,提炼出通用的仓配调度业务流程,落地时将一团臃肿大块的代码按照梳理出的流程节点进行模块结构化重塑,这样做最直接的好处是业务代码逻辑更清晰,且减少重复代码。

按照这个思路,我们梳理出各业务请求的通用处理流程,一共10个流程节点,如下:


通用业务流程的梳理有如下重要意义:

  1. 将共性抽取出来,是能力复用和代码复用的重要一环,这个流程是平台沉淀并对外提供的通用能力;

  2. 这个业务流程是个核心且相对稳定的,类比微内核可扩展架构的微内核部分,不稳定的在扩展层。这对提升系统稳定性有很大帮助。


扩展点设计以支持平台可扩展

一个平台必然会支持各种不同的业务,为了支撑多样性,平台需要提供扩展机制。为此,除了通用业务流程,还需要分离变化点,将其抽象为扩展点和扩展,扩展点可以通过接口来体现,而扩展可以是具体的实现类。对于一个扩展点,不同的业务会使用不同的扩展。

扩展点(extention points)和扩展(extentions)示意图:

这里的模块可以理解为类,除了顶级和叶节点模块外,一个模块含有扩展点,也作为别的模块的扩展,大部分情况下是树形结构。

举例说明,在我们梳理出的通用业务流程中的“可用库存”流程子节点,可用库存由库存中心的在库和在途库存,减去卡单的在库和在途库存构成,这是共性。然而不同的业务还是有差别的,如下图(为展示方便做了简化处理):


  1. 普通的销售订单(主站、TOB等订单调度)-- 使用实时在库良品库存、在途良品库存和履约中心卡单库存。对应InvenServRealTimeImpl + HIServicePromiseImpl类。

  2. 惠生活场景 -- 使用微瑕品在库库存,无在途和卡单库存。对应InventoryServiceDefectImpl类。

  3. 商详页预计送达场景 -- 为性能考虑,使用缓存的在库和在途良品库存,不考虑卡单库存。对应InventoryServiceCachedImpl类。

  4. 开仓模拟场景 -- 和1一样,但卡单库存为0。对应InvenServRealTimeImpl + HIServiceDummyImpl类。

  5. 采购分仓模拟场景 -- 使用无限库存模型,即每个仓库的库存都是无穷的,此时可以mock无穷大在库库存,无在途和卡单库存。对应InvenServInfiniteImpl类。

该类图中,InventoryService是个抽象类(也可以在抽象类上面再定义一个接口),使用了模板方法设计模式,getInventory函数已经实现“可用库存由库存中心的在库和在途库存,减去卡单的在库和在途库存构成”以及其他相关通用逻辑,但是获取在库库存、获取在途库存、获取卡单库存 可以由子类自由定义,子类具体实现时,重载抽象类的protected方法。

在树的下一层,InvenServRealTimeImpl扩展类的getHoldInventory()函数会调用holdService.fetchInventory(),HoldInventoryService是扩展点,有两个扩展实现类。

对接新业务时,如果不能复用现有的扩展,则在相应的扩展点新写子类,这个工作是平台方即我们自己在工程里写,而非像很多平台用写插件的方式来扩展。因为这样更轻量,符合我们的情况。


扩展点设计的重要意义:

  1. 最直接的好处是使得平台可扩展,支持业务在通用能力或现有能力的基础上的个性化扩展和变更。

  2. 不同的业务场景可以使用不同的扩展类,相比大块if else的臃肿耦合代码,隔离性更好。


模块化开发

在实际开发时,用DI的方式进行各个java类的开发,类里的配置数据和扩展点所需扩展在后面都可以用依赖注入的方式注入进来。

上节示例类图中,DispatchService为通用业务流程类,成员inventoryService和splitService有 @Inject注解,HIServicePromiseImpl类的配置数据hiParam也有 @Inject注解,这里使用Guice轻量级DI框架来支持模块化开发,方便后面运行时编排。模块化开发确保代码极强的可测试性,组件间低耦合,提高代码模块化程度。


业务流程编排

前面我们有了通用流程,也分离出了很多扩展点,实现了它们的具体扩展,经过具体的模块化开发后,需要将这些组件有机集成起来,形成一个可用的完整业务流程。

平台通过Guice的运行时动态绑定来实现通用流程、扩展和配置数据的绑定和递归组装。如主站订单调度场景的业务流程编排:

f1ea58d8151643eea70fdbe9bbe23623.png

BizRoleId表示业务身份,结合配置文件热加载,AbstractModule的configure函数动态解析配置文件,则可以动态更新模块。这种配置文件也可以以一定的形式在页面上展示,所有的扩展点和扩展及配置参数也可以可视化展示出来供灵活配置,配置完后以一个业务身份标记并存储起来,再热加载到系统里。

业务流程编排将一个个独立的模块通过Guice的Module特性运行时组装起来,灵活的同时,每个业务场景可以有自己单独的AbstractModule,进一步消除if else,使得各业务代码进一步隔离开来。另外将编排出的业务流程和策略关联到一个唯一的策略ID,存储起来方便历史追溯。

业务接入和身份路由

业务身份路由负责确定请求的业务身份ID,根据ID找到对应的AbstractModule,进而确定其业务流程。

路由逻辑如下:

  1. 新对接的业务场景会在请求中携带其业务身份ID。

  2. 为兼容老的未指定业务身份ID的业务场景的请求,则根据老的判断规则为其指定业务身份ID。

  3. 路由可以方便支持灰度发布和ABTest。如某业务场景的订单指定比例或指定某些地区的为业务身份A,其他比例或地区的订单为业务身份B。

入口层如Controller调用起来也很方便:

YY截图20200120144808.png


平台化落地架构


业务产品层业务产品层是平台对外服务的业务场景,各业务场景在平台上编排自己的业务流程。

路由引擎层路由引擎层确定请求的业务身份ID,进而找到相应的业务流程。

编排引擎层编排引擎层将核心能力层的组件编排成一个个完整的业务流程,并用业务身份ID标识。图中通用流程加上黄色部分标识的扩展和配置数据就是主站订单编排出的调度业务流程。

核心能力层核心能力层承载平台的统一调度模型、各个扩展点和它们的扩展、以及配置数据实例。它们在平台上是一个个相对独立的组件,是平台的调度策略能力合集。

统一日志和指标层每个业务场景常常需要关注自己的一些指标数据,为此平台提供通用日志和指标体系能力。将每个业务流程的调度结果以日志形式收集到数仓,进行指标(如仓库分单比、配送商单量比、成本、当日次日达比例等)计算。各业务产品方可以评价其指标是否在预期范围内,进而考虑是否在路由层或编排层变更或改进其业务流程,实现良性闭环。


总结

本文提出一个轻量可快速落地的平台化方案,相比流程引擎、插件机制等重量级平台化技术,落地起来很简单,是一个可以复用的方案。此次平台化技术改造从核心代码行数、新人学习成本和新业务对接三方面提升研发效能。


物流搜索 版权所有© 2006-2015