会员购是基于二次元文化发展而来的电商平台,无论是众多ACG、手办潮玩,还是梗文化的潮玩玩具,都在通过B站文化社区和会员购生态的融合,推动着内容生产与衍生品消费的相互成就。
会员购商品类型丰富,大体包括手办/出版物/软周/3C等类目,可分为预售/现货/众筹类不同的销售阶段。会员购促销平台提供至运营侧丰富的促销工具,主要可以分为以下几个大类:
- 优惠券:定向/通用优惠券,在品类、品牌、角色、IP等不同维度上进行可用商品的范围圈定;
- 价格减免类:用户正常下单付款时,如果满足活动规则,直接减免一部分支付金额,例如:秒杀、新人专享价、X件Y折、第二件半价等;
- 满返类:商品本身价格不做优惠,在商品支付定金/全款价格之后,予以一定优惠返还,例如早划算、欧气宝箱等;
- 玩法类:在正常下单付款过程中,设置拼团、砍价、提前购买资格等新增额外玩法,使得用户享受优先购买、金额减免等不同的优惠促销形式。
02 促销平台架构升级
运营不断在促销玩法上创造新的促销模式,从应用架构目标层面上,研发侧既希望新玩法能快速上线,又希望系统线上稳定,因此针对现有架构设计暴露出的问题,我们提出“低代码化”的理念,希望能用一套模型和服务流程,能支撑现有及将来可预见的所有促销玩法。
促销平台2.0首先完成了营销活动模型的统一,不仅在平台内定义了一套通用模型定义,也使得和上下游系统集成也会更加清晰。促销活动流程通过沉淀通用组件能力及DSL流程执行引擎,可以灵活的适配不同类型的促销活动,支持定制化流程;在新增促销工具时,能通过统一模型扩展定义新活动类型,并且能快速复用已有的活动组件、定义业务规则和活动流程。
2.1 促销模型统一
促销平台1.0迭代时间较长,由于各类不同活动玩法的增加,系统中共维护了2套活动DB数据库、5套活动领域模型。当需要查询一个商品当前正在参与的活动时,需要根据不同的活动类型,查询不同的活动数据,并最终组装成对应的活动对象;活动的判定流程也因为活动模型不统一,代码中有很多由活动类型路由的组装器、工厂类设计,也出现了工厂类中套用工厂的复杂情况。
因此,促销平台需要统一术语,将促销活动模型拆解为可复用的组件模块,并使用促销元数据模板对组件进行统一描述。我们对已有10+促销工具整合分析,不同促销活动在渠道、用户、风控、商品、权益等子模块领域模型是互通的。通过仓储层组装逻辑,屏蔽多套DB数据带来的数据结构差异性,并面向业务提供统一的促销模型服务。
平台为每一种活动类型定义一套促销模板,拆分为6种组件元数据对促销工具规则进行描述刻画:
- 时间模块:定义活动需要的时间,开始时间、结束时间、预热时间、发奖时间等;并且指定活动对用户露出可见的开始/结束时间,有些活动有预热氛围,则可露出的开始时间为预热时间;
- 用户模块:定义活动可配置的人群类型,包括新客标签和CDP人群等;
- 商品模块:定义活动可配置的商品类型,包括可选择的类目、品牌及商品类型,例如早划算只有定金预售的商品才可以参与,则参与活动的商品类型只有定金预售、没有现货;
- 计次模块:限制活动参与次数,分为限次主体、限次周期和限次类型,例如一个用户账号一天可参与活动一次、一个用户账号一周可领取活动奖励100元等;
- 规则模块:活动参与门槛规则,例如满减门槛、前100名下单、优先购买资格等;
- 权益模块:用户可获得的权益信息,例如价格立减、包邮、折扣等等;
以下是以秒杀为例,定义的促销模板:
{
"triggerAction":"购买中权益",
"timeConfig":{
"timeList":["开始时间", "结束时间", "预热时间", "发奖时间"],
"recallStartTimeType":"预热时间",
"recallEndTimeType":"结束时间"
},
"userConfig":{
"newList":["会员购新用户", "漫画新用户"],
"needCrowd": false
},
"itemscopeConfig":{
"scopeDimList":["类目", "品牌"],
"cateList":["游戏3C数码","可动手办"],
"brandList":["万代"],
"itemsTypeList":["定金预售","现货"]
},
"limitConfig":{
"principal":["用户账号限次"],
"period":["天","周","终身"],
"traget":["累计奖励金额"]
},
"benefitRule":{
"rules":["ruleId1", "ruleId2"],
"benefit":[
{
"type":"价格立减",
"amount":300
},{
"type":"包邮",
"delivery":true
},
]
}
}
在促销系统中,促销配置数据使用redis缓存进行性能提升,而促销活动缓存数据大多都是SKU/SPU商品信息维度的数据,并且采用多级缓存、增加使用本地缓存,对热点商品促销活动查询加速访问性能,降低对Redis节点负载。
2.2 领域规则引擎
促销活动千变万化,规则组件承载了促销模型中最灵活的部分,它包括了参与用户类型、促销购买门槛、促销购买数量、促销奖励上限等等千变万化的规则。我们调研对比了市场上主流的一些框架的优缺点,包括Groovy、Drools、EasyRule、Aviator、QLExpress等。
经过对比,EasyRule提供了一套简单轻量的API接口,没有提供业务领域抽象和集成能力,只是提供了一种通用的设计框架;Drools是一个高性能的规则引擎,但配置文件无论是drl文件还是excel都有很高的学习成本。最终,我们规则引擎采用Qlexpress + 规则组解析 + 规则配置的方案,通过栈结构实现了一套简单的规则条件解析,并行计算规则条件结果,最后通过Qlexpress计算规则的检查结果。在规则引擎中,定义了三个重点部分:
- 规则条件定义:指定规则需要判定的主体对象,及可供选择的操作运算符;
- 规则条件:指定规则条件定义中的操作符和对比右值,生成一个规则条件实体;
- 规则:一组规则条件实例和与或运算符组装的表达式组合。
举个实际的例子,定义一条规则:(用户注册时间大于2021年01月01日 && 支付次数大于1次) || (用户注册时间大于2020年01月01日 && 支付次数大于5次) 。首先,规则判定的用户上下文如下:
{
"userRegisterTime":"2021-08-10 20:00:01.123",
"payTimes": 10
}
规则解析后,可以拆分为四条规则条件,使用了2个规则定义:
使用用户上下文和规则条件计算结果,使用Qlexpress对整条规则最后的计算结果true/false进行计算,获得规则判定的最终结果。
最后关于规则定义中规则节点的脚本热加载实现,目前JRebel热加载机制可以基于JVMTI或类加载器实现,或者使用Groovy动态编译语言,通过脚本代码包装生成class, 然后产生该类实例对象, 再具体执行逻辑代码。
2.3 促销流程统一
在商详页、购物车、结算下单这三块核心交易链路中,活动促销优惠计算逻辑是分开独立维护的,没有形成统一,如下图所示。因此随着促销优惠类型的增加或者促销规则的变化,核心链路的重复开发量会显著加大。
首先,我们先分析影响不同场景下的商品价格计算因素有哪些,即在促销模型中的【权益组件】部分。结合现有的促销工具,可以大致分为以下表格列举的类型。促销权益结合不同的优惠阶段,可以包装成不同的促销活动,例如购买中的权益会直接影响用户下单支付的价格金额,属于及时享受,促进用户的下单支付转化;购买后权益是用户在完成下单支付后获得优惠叠加,可以促进用户的留存和多次消费。任何一直促销工具,可以是在购中或购后的单一权益,也可以是促销权益组合,由运营激励策略决定。
其次,如下图所示,在统一活动模型且确定权益类型后,促销平台2.0将所有的促销优惠查询和算价流程统一,沉淀为通用服务,提供多种维度的权益查询,接入至C端交易链路场景中。平台即能保证所有场景的算价一致性,也使得促销规则修改更加的高效统一,当新增一种促销工具且促销权益类型不发生变化时,依赖系统改动量也会降低到最少。关于不同促销工具的通用流程定义,可以详见2.4部分,介绍了如何适配不同促销工具灵活的流程定义能力。
2.4 促销流程配置化
在统一模型和流程的大背景下,促销平台还需要考虑流程业务抽象化问题,需要提供核心能力+扩展能力来实现不同促销工具和业务场景的定制化需求,将所谓的核心流程和业务定制进行分割。我们需要回答的问题是:如何快速灵活的搭建一种新的促销工具,尽量减少对平台的侵入性。答案是可以,平台需要具备两个能力:“配置化”和“编排化”。
在2.1和2.2中,促销平台完成了领域和接入的统一,我们能够通过促销模板的“配置化”,描述一种新促销工具各组成部分允许配置的范围和约束。那是否有一种形式能刻画每种促销工具的判定执行和权益发放流程的配置化呢?答案是肯定的,在OA领BPMN业务流程管理解决方案,是一种经典的工作流程规范设计。工作流的本质是编排,通过预制的工作流程模板描述编排一个业务流程,每次使用时都通过模板构造流程执行对象,通过模板指导对象完成业务流程的执行驱动和回溯。
调研目前现有的流程开源框架有Activiti、Flowable、LiteFlow等,优势是开箱即用,功能全面,但与之并存的是较为复杂DB和配置维护问题,例如:LiteFlow框架的DSL复杂语法、Activiti7.0引入的20+张数据表。研发在熟悉开源设计的同时,也需要根据业务特性,二次设计适配现有促销业务流程。
所以我们自研了一套轻量级的促销流程配置化能力,轻量级DSL配置+规则引擎+Disruptor队列流程执行:
首先,将促销流程中涉及到的用户判定、限次判定/占用、规则校验、权益发放等流程进行抽象,每个流程都内聚为一个原子节点,沉淀为流程中的可复用组件;
然后,通过json数据结构定义一个简单DSL促销工作流模板,DAG有向无环图刻画节点关系,使用规则引擎判定节点执行规则是否通过,刻画描述流程节点的依赖关系和流程驱动;
最后,使用Disruptor高效环形队列,对流程组件进行并行化执行,提升吞吐;
这样平台可以通过DSL模板配置,快速生成一个新的活动流程模板,在迭代过程中不断的沉淀和丰富通用组件,模版修改和组件的增减可以做到低代码快速上线,提升研发效率(下面为一个简单的秒杀case)。
{
"startNode":"nodeA",
"excuteNodes":[
{
"node":"nodeA",
"componentName":"EventHandler",
"nextNodeList":[
{
"result":"$.code=0",
"nextNode":"nodeB"
},
{
"result":"$.code=0",
"nextNode":"nodeC"
}
]
},{
"node":"nodeB",
"componentName":"TimeHandler"
},{
"node":"nodeC",
"componentName":"UserHandler"
},{
"node":"nodeD",
"componentName":"RiskHandler",
"accessRule":"nodeB.code==0 && nodeC.code==0",
"nextNodeList":[
{
"result":"$.code=0",
"nextNode":"nodeE"
}
]
},{
"node":"nodeE",
"componentName":"BenefitHandler"
}
]
}
03 总结
在促销平台2.0升级的过程中,我们对促销模型、促销流程进行了统一,自建了规则引擎、流程引擎等组件进行沉淀,促销平台也使得toC架构结构分层更加清晰。在此之外,我们还会继续对算价核心策略、用户画像促销策略精细化运营。促销运营方案的变化性与促销系统的稳定本就矛盾,我们期望研发迭代能高效支撑业务的快速发展,而平台会随着业务发展逐渐暴露架构问题,一味追求速度或追求质量就会失衡,架构升级是为了适应业务的发展,也是系统对过往、现在和未来所面临的架构性基础/业务问题的重构。