营销系统表面上是个功能模块,运营配活动、用户享优惠、订单算出最终金额,看起来就是加减乘除。真正做过的人都知道,它是披着功能外衣的风控系统。技术实现不难,难的是把所有业务边界想清楚,因为每一个想漏的边界都是一个资损漏洞。大促零资损不是写代码写出来的,是把每一个叠加、互斥、防套利的场景穷举出来堵出来的。
我在分期乐做营销那几年,处理过上亿 GMV 的活动,也踩过几个刻骨铭心的资损坑。这篇文章不讲营销玩法有多少种,讲营销引擎真正难的地方,规则怎么配置化、叠加互斥怎么不漏、优惠组合怎么求最优解、以及算出来的结果怎么保证一致性。这些是营销引擎能不能扛住大促的命脉。
营销计算是分层的
满减、满折、赠品、立减,这些玩法看起来花样百出,抽象成一句话就是,在订单金额上施加一组计算规则。规则怎么施加,先后顺序是什么,决定了最终金额。乱算一气,金额就错。
图:营销计算分商品级、店铺级、平台级三层,每层输出作为下层输入基数,顺序固定不可乱
生产级的营销计算是分层的。第一层是商品级优惠,比如单品的限时折扣,直接作用在商品本身的价格上。第二层是店铺级优惠,比如店铺满减、店铺券,作用在商品级优惠算完之后的金额上。第三层是平台级优惠,平台券、红包、积分抵扣,作用在店铺级优惠算完之后的金额上。每一层的输出是下一层的输入基数,层层递进算出最终实付金额。
这个分层顺序不是随便定的,它对应的是优惠的真实作用域。商品级优惠改的是商品本身的价格,店铺级优惠是店铺对这单整体让利,平台级优惠是平台层面的补贴。基数变了,优惠的意义就变了。把顺序搞反,比如先算平台满减再算商品折扣,等于在没打折的基数上满减,用户会少付钱,平台多亏钱。所以计算顺序是营销引擎的第一条铁律,固定、明确、不可乱。
淘宝那套最优价格计算引擎,底层就是这个分层模型的复杂版本。理解了分层,才算摸到营销计算的门道。
规则引擎,让规则脱离代码
最朴素的营销实现是硬编码。运营要加一个满 100 减 20 的活动,开发写一段 if 判断,发版上线。这种做法在活动少、变化慢时凑合能用,一旦活动频繁就崩了。运营每周加三五个活动,开发天天改代码发版,代码里堆满了各种条件分支,三个月后没人敢动,一动就出 bug。
规则引擎要解决的就是这个问题,把规则逻辑和业务代码分离。运营在后台配置规则,引擎读取执行,加活动不用发版。一个规则抽象成条件和动作两部分,条件是满 100 元,动作是减 20 元。引擎拿到订单上下文,判断条件满不满足,满足就执行动作。复杂的规则用 DSL 表达,像 Drools 的 DRL 那样。
rule "满100减20" when Order(totalAmount >= 100, category == "general") then Order.setDiscount(20);end选规则引擎时我们走过弯路。早期想用现成的流程引擎 Flowable、Activiti 这类,后来发现它们是为审批流、工作流设计的,节点是人工任务和分支跳转,拿来跑营销规则这种高密度计算既别扭又慢。最终自研了一套引擎,核心是责任链加模板方法加策略模式。责任链把一个订单的计算拆成有序的处理节点,模板方法固定计算骨架,策略模式让每种优惠类型自己实现具体算法。这套结构既灵活又可控,运营配的活动规则进来,引擎按既定骨架执行,不会跑飞。自研引擎的好处是你完全掌控行为,坏处是前期投入大,但对于营销这种规则高频变化的场景,这笔投入值得。
叠加与互斥,资损的高发区
规则引擎解决了单个规则怎么跑,但营销真正的深水区是多个规则一起跑时的叠加和互斥。资损事故十有八九出在这里。
图:叠加互斥的三种资损坑——该互斥没互斥、叠加顺序错、跨店铺叠加,解法是维护显式互斥矩阵
叠加指的是多个优惠能不能一起用,店铺满减免后再用平台券,这是叠加。互斥指的是两个优惠只能选一个,限时折扣和满减不能同时享。听上去简单,坑全在细节里。
第一种坑,该互斥的没互斥。限时折扣和满减同时生效了,用户用 5 折后的金额又满足了满减门槛,等于在一个已经打过折的低价上再满减,套了两层优惠。这种漏洞一旦被发现,羊毛党会在几小时内刷出巨大资损,大促时尤其惨烈。
第二种坑,叠加顺序错。先算满减再打折,和先打折再算满减,结果可能差很多。规则引擎必须固定叠加顺序,不能让两套规则各算各的。
第三种坑,跨活动跨店铺叠加。一个订单含多个店铺的商品,不同店铺的活动不该叠加却叠加了,或者一个店铺的活动作用到了另一个店铺的商品上。
要堵住这些坑,靠代码里零散的 if 判断是没希望的。正确的做法是维护一张显式的互斥矩阵,每两个活动类型之间,能叠加还是互斥,在这张矩阵里查。新建一个活动类型,先更新互斥矩阵,再开发功能。把互斥关系当作一等公民管理,营销引擎才算上了道。我带团队时立过一条规矩,任何新活动类型上线前必须过互斥矩阵评审,没过的不准发,这条规矩帮我挡住过好几次潜在资损。
每两个活动类型之间能不能叠加,必须有一张显式的互斥矩阵,而不是靠代码里的隐含逻辑。新建活动类型先更新矩阵再开发。资损事故 90% 源于互斥规则缺失或冲突。把互斥关系当一等公民管理,是营销引擎的命脉。
优惠组合的最优解
一个订单能用券 A 也能用券 B,能参加满减也能参加折扣,这些组合里哪个对用户最划算,这是优惠最优解问题。
朴素做法是枚举所有合法组合,受互斥规则约束只考虑合法的那些,每种组合算一遍最终金额,返回最优解。思路简单,问题在于组合数会爆炸。一个订单有十几个商品、能匹配五六个活动时,合法组合的数量是指数级增长的,全枚举算不动。
生产里要做剪枝。明显劣势的组合提前剔除,比如组合 A 比组合 B 优惠力度小,且 A 能用的券 B 都能用,那 A 一定不优于 B,直接不计算 A。通过这类支配关系剪枝,能把搜索空间压下来。同时缓存常用组合的计算结果,相似订单不重复算。这套优化做下来,大促时每秒数千笔订单的优惠计算能扛住,响应时间控制在几十毫秒。
注意一点,最优解是站在用户角度的最划算,但营销系统还要考虑平台成本。有时用户最划算的组合对平台资损最大,平台会通过活动互斥、优惠上限来约束。最优解引擎要在用户利益和平台成本之间找到平衡点,这又回到了互斥矩阵的设计上。
算出来的结果要可重现
营销计算不是算一次就完了,退款、争议、对账时都要拿这笔订单当时的优惠来算。如果每次都按实时规则重算,活动可能已经结束、规则可能已经改了,算出来的结果对不上原始支付金额,资损就来了。
所以营销系统必须做规则快照。下单时把当时命中的活动规则版本和计算结果一起存下来,作为这笔订单的优惠快照。后续任何退款、争议、对账,都按快照重算,不查实时规则库。这是营销一致性的根本。
金额层面还有一条恒等校验,优惠明细各项之和,必须等于订单原价减去实付金额。每次计算完都校验一遍,校验不过说明计算有 bug,直接拦截。这条恒等式看着简单,挡住过不少边界 bug。
防套利是营销系统的另一面。账号维度,同一用户参加同一活动有次数限制。设备维度,同设备多账号要限制。行为维度,异常的下单退款循环要检测。金额维度,单笔和累计优惠有上限。这几条维度一起上,才能把羊毛党的空间压到最小。
活动要有紧急终止的能力
一个活动从草稿、审核、待生效、进行中到结束,是一条标准生命周期。但比正常生命周期更重要的是紧急终止能力。
图:活动从草稿到结束的标准生命周期,任一阶段可触发紧急终止,要求热更新秒级生效止损
营销规则一旦上线,就可能在任何时候被发现漏洞。也许是运营配置时漏配了一个互斥,也许是规则引擎的某个边界判断写错了。漏洞一旦被羊毛党发现,资损是按分钟累积的。这种时候能不能在一分钟内把活动紧急终止止损,是营销系统的生死线。
紧急终止要求活动状态可以热更新,引擎实时感知,下一个请求进来就不再命中这个活动。不能等到重新发版、不能等到缓存过期。我们做大促时专门有值班同学盯监控,优惠金额异常飙升的告警一响,第一反应是终止活动止血,然后再定位问题。先止血再治病,这是资损处置的铁律。
既灵活又安全,靠的是把边界想透
营销引擎的技术实现其实不复杂,规则引擎加分层计算加互斥矩阵,搭出来一个能跑的系统不难。难的是把所有业务边界想清楚。
互斥关系有没有覆盖全,规则快照保没保住一致性,紧急终止能不能秒级生效,防套利有没有堵住羊毛党的路。这些细节才是大促零资损的真正来源。我经手过几次大促零资损,背后不是某一次神来之笔,是几十个边界场景一个个穷举、一条条堵出来的结果。
把这套规则体系建好,营销引擎才能既灵活又安全。灵活是运营想要的,能随时加活动改规则。安全是平台要的,不能出资损。这两者天然有张力,工程上能调和它们的,就是把每一个叠加、互斥、套利的边界都显式建模、统一管理,让引擎的行为可预测、可审计。这件事做扎实了,营销系统才扛得住真实的大促洪流。








