全链路营销|基于事件驱动的流程编排系统 策略中心系统
全链路营销|基于事件驱动的流程编排系统 https://mp.weixin.qq.com/s/RHXyGaGyp_CK7FJPDqS3Cg
全链路营销|基于事件驱动的流程编排系统
阿里妹导读
本文主要介绍了 AE 策略中心的技术方案选型与落地实战。
项目背景
全链路营销是去中心化的运营方式,给用户发放精细化的营销权益,打造策略中心系统。根据用户的行为记录用户的喜好商品,在满足策略中心规则后,通过 C 端链路的触发实现营销权益发放和权益表达。
架构设计
架构调研
当前营销各应用都是采用 TMF 框架,由中台提供标准的节点,营销业务层进行节点编排,每个节点都提供相应的拓展点,可以让不同营销工具实现不同的营销玩法。
上图可以看出营销计算的流程是比较固定的,业务迭代一般在当前流程的节点上进行拓展,TMF 提供的拓展点就是采用策略模式。比如文案构建节点,不同的营销工具需要返回不同的文案,根据营销工具类型匹配文案构建类,执行业务规则。
但是全链路营销面向的是玩法层,所以每次迭代的业务流程都是不固定的,对用户的行为要求是不固定的,对下游的依赖也是变化的。所以根据不同的玩法模板,执行不同的业务流程节点,这样做后续的拓展性会比较好。
全链路营销具有两个链路:数据准备和权益发放,数据准备阶段是用户行为规则校验通过后通过 MQ 消息触发,权益发放阶段是在用户在访问某个资源位时,由上游调用策略中心触发。
用户的每个行为对我们来说都属于事件,只不过有的是同步触发,有的是异步触发,所以依赖不同的事件来编排不同的业务节点,编排方式可以通过配置中心或者 Java 硬编码的形式,拓展性和灵活性更高,整体建设了一个基于事件驱动的流程编排系统。
系统架构
策略中心将不同事件通过不同的渠道(channelCode)进行标识,不同的 channelCode 触发不同的流程编排。
策略中心的代码架构整体分为 4 层:
- 接入层:
- 用户的行为校验,由行为规则域统一收敛,满足规则后发送 MQ 消息通知策略中心
- 用户触发某个资源位的行为,由上游同步通知策略中心
- 服务层:分渠道进行事件处理,然后统一调到领域层进行策略执行
- 领域层:策略域包含多个子域,可自主编排多个子域的域能力,最后对外提供策略域能力
-
基础层:基础设施层,包含外域依赖和存储依赖两个部分
流程设计
策略中心整体代码流程实现,如下图所示:
- 行为事件:代表用户完成了某个行为规则,MQ 消息中携带 ruleKey 作为 channelCode,策略中心会通过渠道查询策略活动,构建策略实例,完成数据的准备阶段,包括查询用户操作的商品、算法召回、处理商品折扣等,最后将策略实例的相关数据(商品、流程)存储到 Redis。
-
资源位事件:代表用户点击了某个资源位,API 的入参中有 channelCode,策略中心同样进行活动查询、策略构建,然后从 Redis 中读取数据准备阶段的数据,填充到策略实例,开始进行权益的发放。
设计说明:
- 策略实例的驱动逻辑固定,不同玩法对应不同的策略模板,模板会编排不同的节点形成执行器链,新增一种玩法只需要新增一个策略模板即可,不会对其他玩法产生影响,拓展性好。
- 分布式锁保证同一个策略实例在同一时刻只能被一个线程执行,如果行为事件和资源位事件同时执行,会造成数据覆盖的问题。
-
不涉及数据覆盖问题的场景不需要加锁,所以可以通过入参或者策略模板配置来决定是否加锁。
模型设计
领域模型
策略域的聚合为 TacticsInstanceContext,也就是上文一直提到的策略实例,聚合根是 TacticsActivity 策略活动。每个策略实例 TacticsInstanceContext 中包含一个策略活动和相关的执行参数以及执行结果,执行参数包含买家 ID、渠道、是否加锁、是否仅执行最优策略等信息,所以一个策略活动和一个用户唯一对应一个策略实例。
TacticsInstanceContext 只包含基础的策略执行数据,但是策略层有多个子域,各个子域对 Context 的依赖不同,所以需要对策略实例做能力的拓展,常见的方式有:
-
Context 中提供 map 拓展字段,将所有节点所需的数据统一放入拓展字段中,Context 中提供充血方法获取对应的数据。
-
提供能力拓展的接口,增加 Context 子类实现拓展接口,节点执行前判断入参是否有该能力,在进行执行。
我们采用接口拓展的方式解决该问题,因为项目的核心设计思想就是策略 + 工厂模式,可以根据不同的策略模板(templateType)构建不同的 Context。
比如全链路营销 3.0 版本需要用户的商品数据,所以进行接口 IItemContext 的拓展:
策略模板
每种玩法对应一种策略模板,目前有全链路营销 2.0 和全链路营销 3.0 两个模板,所以通过模板工厂去获取模板的配置:
上图可以看出,getProcessorChain() 提供了核心的流程编排能力,因为每个事件对应一个 channelCode,所以流程编排是基于事件标识来完成,代码示例:
public List<String> getProcessorChain(String channelCode) {
List<String> processorChain = Lists.newArrayList();
// channelCodeA 或 channelCodeB 理论上都是一个可配置的Set集合,这里进行简化
if ("channelCodeA".equals(channelCode)) {
// 查询商品
processorChain.add(QueryacticsProcessor.PROCESSOR_NAME);
// 处理商品营销数据
processorChain.add(HandleItemPromotionTacticsProcessor.PROCESSOR_NAME);
// 记录行为事件消费成功
processorChain.add(EventTacticsProcessor.PROCESSOR_NAME);
} else if ("channelCodeB".equals(channelCode)) {
// 校验行为事件是否消费成功
processorChain.add(CheckEventTacticsProcessor.PROCESSOR_NAME);
// 规则校验
processorChain.add(RuleCheckTacticsProcessor.PROCESSOR_NAME);
// 查询资产
processorChain.add(QueryAssetTacticsProcessor.PROCESSOR_NAME);
// 权益发放
processorChain.add(SendBenefitTacticsProcessor.PROCESSOR_NAME);
// 构建结果
processorChain.add(BuildResultTacticsProcessor.PROCESSOR_NAME);
}
return processorChain;
}
节点模型
接口类:
public interface TacticsProcessor {
/**
* 节点名称
*/
String getProcessorName();
/**
* 策略实例匹配校验
*/
boolean validate(TacticsInstanceContext instanceContext);
/**
* 策略实例执行逻辑
*/
void process(TacticsInstanceContext instanceContext);
}
拓展子类:
幂等设计
业务玩法是循环发放权益,但是只有当前用户不存在可用的资产(未领取、过期、核销等),才进行发放。
权益系统可以保证一个幂等 ID 只会进行一次的资源扣减和资产的写入动作,并且当一次请求失败时,权益系统内部会主动重试或回滚,所以策略中心为了防止超发问题,做了两件事:
- 保证幂等 ID 的唯一性,可重入性;
-
当存在可用的资产时,直接返回该资产,不进行权益发放;
当用户资产表查询出现抖动或者其他情况时,我们将发放的次数置为 0,第几次发放 willSendCnt 置为 1。如果用户是第一次领取,那么会执行真正的权益发放,符合业务流程;如果用户非首次领取,因为相同的幂等 ID 只会扣减一次资源,所以不会造成超发。
项目总结
本文主要介绍了 AE 策略中心的技术方案选型与落地实战。从最初版的逻辑平铺的技术设计,到基于事件驱动的流程编排系统,我们做了系统架构的优化和提升,未来可拓展性更强,业务迭代只需要增加新的的策略模板和节点即可,不会影响其他策略模板逻辑,符合开闭原则。