说到返利优惠券机器人,很多人的第一反应是“不就是定时发券嘛”,但真正落地过的人都知道,难点从来不在“发券”这个动作上,而在于搞清楚“该给谁发、什么时间发、用哪种姿势发才能不让人反感”。我做过几个类似的项目,踩了不少坑,也沉淀了一套基于Java和Flink的实时计算方案,核心是动态规则引擎。这篇就把整个构建思路、关键设计和实战中的坑一次性讲清楚。
刚接手这个项目时,运营给的需求很简单粗暴:用户只要加购了商品但没下单,就要给用户发一张优惠券。等代码上线后问题马上来了——领取率倒是上去了,但核销率极低,ROI完全算不过账。后来仔细看数据才明白,不加区分地把所有“加购未购”的用户都当成同一种人,本身就是错的:有人是比价阶段,有人是等大促,有人就是手滑点了一下加购。不同动机的用户,需要的券种、面额、触达时机完全不一样。这就需要一个能基于实时行为动态判断用户意图的系统,而不是一套写死的if-else规则。
1. 为什么返利优惠券场景必须上实时计算
先说结论:不是所有优惠券场景都需要实时计算,但凡是涉及“行为触发”的精准触达,用离线批处理或者定时任务,体验上一定会有明显的断层。
拿我之前一个项目举例,业务方希望用户“连续三天浏览同一品类但未下单”时触发一张品类券。如果走离线数仓,T+1才能算出来,等用户看到这张券,可能已经去别的平台买了,券就白发了。而且离线任务算的是“昨天的状态”,跟用户当下的意图是脱节的。
但实时计算不是银弹,它解决的是“在正确的时间窗口内感知用户行为变化”的问题。Flink在这里承担的核心职责有两块:
- 在事件流上做滑动窗口聚合,识别用户短时间内的行为序列
- 将聚合结果与规则引擎中配置的动态阈值比对,命中后立即触发触达
为什么选Flink而不是Spark Streaming?对于这类场景,Flink的架构设计本质上更贴合需求:真正的流式处理(每条数据逐条处理,延迟更低)、原生的事件时间语义(处理乱序到达的埋点数据时不用自己写一堆补偿逻辑)、强大的状态管理机制(跨窗口保存用户行为状态,而不只是算完就丢)。Spark Streaming本质上是微批次,最短也要几百毫秒的攒批延迟,在需要“秒级感知用户行为变化”的返利场景下会显得迟钝。
还要回应一个常见的疑问:既然规则引擎可以把规则抽象成SQL,那能不能直接用Flink SQL做?如果业务规则只有两三条,可以,一旦规则数量超过几十条、且需要频繁调整阈值,纯SQL方案会变成一个噩梦。SQL的优势在于声明式表达,换来的是对引擎内部的控制力下降——类似“用户最近5分钟浏览超过10次且加购超过2次”这种规则,用SQL写没问题,但“IF用户触发A事件, THEN在90秒内观察是否出现B事件, 否则推送补偿券”这类有状态、有时序逻辑的规则,SQL就不太舒服了。所以我在项目中选用的是“Flink做实时计算底座 + 自研轻量规则引擎做决策”的混合架构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体技术方案与架构拆解
整个系统的数据链路分为五层,每一层都有明确的职责边界。我把架构图画在脑子里是这样的:
2.1 数据接入层:从埋点到Kafka
用户行为数据从App、H5、小程序端采集后,统一上报到数据网关,经过格式校验和字段补全,写入Kafka。Topic按业务域拆分,核心的两个是:
user_action_topic:用户的浏览、点击、加购、下单、支付行为user_profile_topic:用户的基础属性变更、会员等级变化
Kafka的分区键按userId哈希,保证同一个用户的行为数据落到同一个分区,这是Flink端做状态操作的前提条件。如果不这样做,同一个用户的行为分布在多个分区,后续做会话窗口和状态更新时就要跨分区合并数据,延迟和复杂度都会上升。
注意:埋点的数据质量直接决定实时计算的下限。我在项目中专门加了一层数据校验算子,过滤掉缺失关键字段的脏数据,同时把过滤掉的记录输出到旁路日志,方便后续排查埋点问题。这一步看起来不起眼,少了它,后面所有规则的准确性都无从谈起。
2.2 计算引擎层:Flink集群的核心职责
Flink集群消费Kafka中的用户行为数据,主要做三类计算:
- 会话拆分:将用户的连续行为按时间间隔切分成一个个会话(比如30分钟内无新行为则切分新会话)
- 窗口聚合:对会话内的行为按滑动窗口做计数、金额求和、品类统计
- 特征拼接:将窗口聚合结果和用户基础画像拼接,形成一条完整的“用户当前状态向量”
这里有一个关键技术点:状态的生命周期管理。Flink的状态默认是永久保留的,但用户行为特征有很强的时效性,过期的状态不仅占用内存,还会导致规则误判。比如“最近15分钟加购了3件商品”这个规则,如果30分钟前的加购行为还残留在状态里,计算就会失真。我在状态上配置了TTL(Time To Live),按规则关注的最大时间窗口设置过期时间,既保证计算精确度,又控制状态大小。
2.3 规则决策层:动态规则引擎的定位
规则引擎不直接消费Kafka数据,而是挂在Flink计算链路之后,接收当前用户的行为特征向量,输出命中的规则列表和对应的优惠策略。这样做的好处是把“算状态”和“做决策”两个关注点拆开:Flink只负责实时维护“用户现在处于什么状态”,规则引擎只负责判断“当前状态下应该做什么动作”。
这样的解耦带来一个操作上的便利:修改规则不需要重启Flink任务。规则引擎是独立部署的服务,规则变更通过RPC接口或配置中心下发,实时生效。Flink任务本身不感知规则内容,它只负责提供特征——这正好呼应了标题里“动态规则引擎”的核心含义。
2.4 触达执行层:收到指令后的动作编排
规则引擎命中规则后,会生成一个触达指令,包含:用户ID、触达渠道(Pus
