知识简介:通过LiteFlow规则引擎构建会员权益体系,实现权益节点可插拔,可编排,可复用的特性。完成会员权益数据底盘建设,将分散的权益数据集中,提升权益查询及管理水平。
历史痛点
1)不同等级权益列表均为硬编码实现,难以做到权益的动态调整。(顺序,增删);
2)权益迭代由不同的服务及人员开发,数据分散在不同库中,维护困难;
3)权益领取状态需调用不同服务请求,响应时长不可控,需建设统一的权益查询归属;
4)权益列表多接口串行查询,性能差。
建设流程
一、搭建实时权益数据底盘
构建权益底盘表,实时同步权益领取状态。为校验数据准确性,将原数据源信息同步至BDP,T+1校对数据。
二、基于规则引擎编排权益
基于规则引擎的开源方式、学习成本、社区活跃度、性能与支持度,权益体系综合考虑使用LiteFlow 作为权益节点编排工具。
1) 为什么要用规则引擎,跟会员权益的契合点在哪里?
研发角度:
· 逻辑复杂 顺丰会员根据会员等级,享有不同的权益项,且根据渠道,时间,权益优先级展示不同的权益列表,其中每项权益又需要校验不同的领取规则,要使用大量if-else来实现或者设计模式。但过于复杂的规则逻辑,使用设计模式也往往是存在大量并且关系复杂的类,导致代码难于维护,对新加入的同学极不友好。
· 变更时需要从头梳理逻辑,在适当的地方进行if...else...代码逻辑调整,耗费大量时间进行梳理。
· 开发周期较长,需求发生变更时,需要研发人员安排开发周期上线,对于快速变化的业务,传统的开发工作方式显得捉襟见肘。
产品角度:
· 期望能够实现热部署,对于权益的下线和权益的优先级列表,产品期望可配置式的变更。
· 降低需求变动的时间成本,快速验证发布。
2)会员权益继承规则引擎的两大特性
· 将瀑布流式的代码,转变成以权益组件为核心概念的代码结构,这种结构的好处是可以任意编排,权益组件与组件之间是解耦的,组件之间的流转全靠规则来驱动。
3)基于规则引擎构建会员权益
· 构建权益组件 将会员权益抽象出来,每项权益定义为一个组件,在组件内将当前权益所需结果数据封装至流程上下文中。
权益类继承NodeComponent,并通过@LiteflowComponent注解标识权益节点并命名。权益节点校验流程:每项权益需进行权益前置校验(是否满足权益展示条件),权益领取校验,之后构建权益返参上下文,组合流程数据。(升级有礼为例)
· 权益节点编排。根据权益使用场景、会员等级、权益优先级等,构建不同的权益调用链。会员权益编排涉及两大场景,一是会员首页的会员福利模块,展示当前用户待领取及已领取权益。二是各渠道运营位的弹框,增加会员权益的曝光。
(1)会员福利模块的权益编排:(不同等级用户调用链上权益组件不同,下图为V7等级调用链及权益组件)
在权益底盘建设前,为提升接口响应效率,采用多权益并行编排。下图为通过XML中配置THEN+WHEN的方式实现调用链配置。
底盘建设完成后,所有权益领取状态在前置节点查询出来,通过上下文传递到各节点,完成结果数据的拼装。
权益列表前端效果展示:
(2)推荐权益编排:跟权益列表需要走完所有权益组件不同,推荐权益在命中后,会直接走到后置节点组件,装配权益文案及图片流程数据后返回。
下图为通过XML中配置THEN+FINALLY后置节点方式实现V0用户的调用链,接口调用发现生日有礼还未被推荐过时,则直接跳 转到后置节点(下图虚线权益组件将被跳过),完成生日有礼的文案图片配置,将结果数据返回。
推荐权益前端效果展示(APP会员首页运营位)
注意事项
1) 并行编排 注意使用线程安全的队列
权益列表上线后,每日会有20个左右的空指针(由于异常被捕获和前端兜底,不会对业务造成影响)
排查异常出现在最终列表排序阶段:
equityVo.getReceiveEquityLists().stream().sorted(Comparator.comparing(ReceiveEquityVO::getIsToReceive,Comparator.reverseOrder()).thenComparing(ReceiveEquityVO::getSort)).collect(Collectors.toList());
本地模拟调用,发现receiveEquityLists中会有空对象出现,将并发模式修改为串行调用则无此现象。观察receiveEquityLists使用ArrayList实现,在并发编排中线程不安全,修改为CopyOnWriteArrayList后问题解决。
故,在使用并行编排时。注意使用线程安全的队列。例如ConcurrentMap,CopyOnWriteArrayList
2) 内存缓存的配置
推荐权益中使用的文案及图片,均在会员配置表中配置,并使用@Cacheable提供本地缓存能力。测试中需要对某项权益图片进行替换,库中更新后,本地缓存较长时间内并未更新成功。查看原有本地缓存配置项:
caffeine.expireAfterAccess=300
caffeine.refreshAfterWrite=300
更新配置为
caffeine.expireAfterWrite =300
caffeine.refreshAfterWrite=300
配置项说明:
expireAfterAccess为访问过期,即有访问则自动续期
expireAfterWrite 当缓存项在指定的时间段内没有更新就会被回收
refreshAfterWrite 当缓存项上一次更新操作之后的多久会被刷新