资讯动态

百万级用户营销平台架构设计实战:分层、选型与稳定性治理

发布时间:2026/10/21 10:08:55 来源:尧图企业网站定制
前阵子我们团队接到一个「老系统现代化」的活儿旧营销后台每天靠定时脚本扫库发短信几万条就能把数据库拖到告警而业务方给的新目标是——支持百万级用户的个性化营销包含人群圈选、实时行为触发、多渠道触达和完整效果分析。这个标题叫“架构设计实战”那我就直接从工程落地的角度来讲。这里不聊具体的算法模型怎么调参重点讲清楚面对百万级用户量级的营销业务整体架构怎么分层、核心链路怎么设计、组件怎么选型、容量怎么估算、线上会踩哪些坑。想了解营销业务建模的算法同学可以绕道这篇文章适合后端架构师、技术负责人以及准备从零搭营销中台的团队参考。1. 业务目标与核心矛盾百万级营销平台究竟在解决什么问题1.1 营销平台不是“发短信”这么简单很多人对营销平台的认知还停留在“搭个管理后台上传用户名单点一下发送”。但实际上一个真正能支撑百万级用户的智能营销平台核心是一条完整的数据闭环用户画像标签 → 人群圈选 → 策略匹配 → 频控校验 → 多渠道触达 → 回执回收 → 效果分析 → 数据回流优化每一步都有自己的工程挑战。先说人群圈选运营人员在前端勾选“近30天有购买、性别为女、会员等级大于等于黄金”系统需要在秒级返回符合条件的用户集合。这个动作背后是标签数据的存储和高效查询。百万级用户单看数据量不算夸张真正麻烦的是标签维度组合爆炸——几十上百个标签任意组合总不能每个组合都提前建一张表。策略匹配又是另一层复杂度。营销不只是“给所有人生成同一条消息”而是针对不同人的实时状态做差异化决策。比如“用户把商品加入购物车15分钟还没下单推送一张优惠券”这类实时触发链路要求系统在用户行为发生后几百毫秒内完成决策。再加上模型推理的场景——给用户算一个流失概率高于阈值才触发挽留策略——这一步又会引入单独的推理服务。等这些都做完了真正的硬骨头才出现触达。单一用户可能同时命中多个活动总不能每个活动都发一条短信用户会被轰炸到投诉。所以必须有全局频控、活动频控、通道频控三级限制。触达通道也各自有吞吐上限短信供应商每秒就让你发几百条你一次性把50万条消息怼过去通道直接封你账号。1.2 三个绕不开的工程难点我把这个平台拆到最后核心矛盾集中在三件事人群圈选的时效性。批量营销任务要求在分钟级算出百万级用户集合实时营销任务要求在秒级判断单个用户是否命中某个策略。策略决策的稳定性。规则要有版本管理、灰度发布、审核记录模型推理服务要能在高峰期扛住突发流量不能因为一次大促就把决策引擎打崩。触达链路的可靠性。消息可能发重复、发漏、发错人通道可能抖动、限流、挂掉。系统必须有一套完整的状态机、重试和补偿机制。这三个问题互为耦合。比如你人群圈选变快了但策略决策跟不上消息一样会堆积你触达链路做得再稳人群算错了发出的全是错误营销。所以架构设计的时候不能只盯着某一个局部去优化而是先定好边界。1.3 架构设计原则先定边界再谈技术我们团队定下四条原则后续所有模块设计都围绕它们展开按业务域拆服务不按技术栈拆服务。人群、策略、触达、模板、任务每个都是独立的业务域各自有独立的存储和部署单元避免将来一个人群查询把全站拖垮。批量链路和实时链路共用逻辑但走不同通道。批量任务是离线算好人群快照再定时发送实时任务则靠消息队列和实时计算触发两者不能互相挤占资源。面向失败设计。默认下游会挂、消息会丢、重试会风暴。所有接口必须幂等所有链路必须有重试和兜底。一切可观测。从一次营销任务的创建到人群计算、策略命中、消息发出、回执返回全链路必须有 traceId 串联否则线上出了问题你根本无从查起。原则定了后面的架构选型才不会变成“哪个技术火就上哪个”的拼盘。2. 整体架构与模块边界一张分层蓝图2.1 分层架构总览先放一张整体的分层图用文字描述就是这样的结构营销后台 / OpenAPI 网关 ↓ 平台服务层人群服务 | 策略服务 | 任务调度服务 | 触达服务 | 模板服务 ↓ 决策引擎层规则引擎 | 模型推理服务 | 频控服务 ↓ 数据管道层Kafka → Flink 实时链路 | Spark/Hive 离线链路 ↓ 存储层MySQL | Redis | ClickHouse | 对象存储这套架构里的每一层职责都相对独立互相通过接口或者消息通信而不是直接把数据库表暴露给对方。我下面逐个说明每个模块的职责边界。2.2 关键模块职责定义人群服务负责标签的写入、查询和圈选。运营配置好圈选条件后人群服务把条件翻译成存储引擎的查询语句支持批量圈选生成人群快照和实时校验判断某个用户是否在人群中。策略服务负责策略的配置、版本管理、灰度发布。策略包含“条件”和“动作”两部分条件可以是规则表达式也可以是模型打分阈值动作则指向触达模板。任务调度服务负责批量营销任务的执行计划拆解。一个100万人的任务会被拆成多个分片按时间窗口和通道配额逐步释放避免出现瞬时洪峰。触达服务面向所有触达渠道做统一抽象短信、Push、站内信、邮件都实现同一套接口。负责模板渲染、频控校验、通道限流、重试和回执处理。模板服务管理消息模板和文案变量的渲染。模板必须经过审核才能使用变量渲染不允许出现“亲爱的null”这种低级错误。频控服务独立部署提供高频的频控校验接口核心是“同一用户、同一类型、同一时间段最多触达N次”这类规则。这些服务之间全部通过消息队列解耦。任务调度服务不直接调用触达服务而是往 Kafka 里丢一条“待触达任务”触达服务消费消息去执行。这样做的核心好处是即使触达服务挂了消息还在队列里积压恢复后可以继续消费不会丢数据。2.3 为什么要拆成两套链路批量链路与实时链路这是整个架构里最容易踩坑的地方。我开始以为一套链路就能通吃结果批量任务和实时任务互相干扰最后被迫拆开。批量链路典型的场景是“明天上午10点给100万用户发Push”。人群服务提前离线算好人群快照存成文件或者标记表到点后任务调度服务按计划把人群分片推给触达服务。这条链路的特点是数据量大、时间可控、并发可控。实时链路典型的场景是“用户加购15分钟未支付推送优惠券”。用户行为事件实时进入 KafkaFlink 做过滤和画像更新再触发决策引擎判断是否命中策略。这条链路的特点是单次流量小但峰值QPS不可控必须靠队列削峰。拆开之后两条链路共用策略配置和频控服务但数据路径完全隔离。批量任务就算把触达服务打满也不会影响实时链路的决策延迟反过来实时流量突发时也不至于吃掉批量任务的处理能力。3. 核心选型及背后的取舍逻辑3.1 消息队列与实时计算Kafka Flink 为什么是稳妥解这套架构里消息队列是绝对的命脉。我见过有团队因为图省事直接用 Redis 的 List 做消息队列前期确实能用一旦积压稍微大一点就彻底失控——消费慢了所有消息堆在内存里进程一重启全没。我们最后直接采用 Kafka并且定了一条规矩所有跨服务的异步调用都必须走 Kafka不允许直接用 HTTP 去调用一个可能重试的接口。Kafka 解决的不只是削峰解耦还有消息回放能力。有一次线上发现模型服务发布了一个坏版本把部分用户误判成了高流失人群发了一批不该发的优惠券。问题发现后第一反应不是去改数据而是把 Kafka 里对应的行为事件重新消费一遍把画像和策略命中结果纠正过来。如果没有消息回放能力这种事故的补救成本会高到无法接受。实时计算层选型我们对比过 Flink 和 Spark Streaming。Spark Streaming 在微批模式下延迟在秒级某些场景还勉强能用但营销侧有很多“用户连续点击5次没下单”这类需要跨事件做窗口计数的需求Spark 的微批处理要维护跨批状态实现起来很别扭。Flink 的优势在于原生的窗口机制和精确一次语义配合外部状态后端实时标签更新和触发检测都能做到毫秒到秒级延迟。如果团队对 Flink 不熟也可以先用 Kafka Streams 过渡但长期看 Flink 在窗口计算和状态管理上省心得多。3.2 ClickHouse 做人群圈选查询模型与数据建模人群圈选这张表是整套系统里我最想多说两句的地方。最开始我们图方便把用户标签表放在 MySQL 里几十个标签就是几十个字段运营一提交圈选条件SQL 就变成WHERE tag11 AND tag2 IN (...) AND tag3100。这种查询在没有合适索引的情况下MySQL 只能全表扫描百万用户眨眼就扫一遍但十几个这样的并发查询同时打过来数据库直接喘不上气。后来我们把标签数据迁到了 ClickHouse。核心考量是标签查询本质是列式分析ClickHouse 的列存压缩比高、向量化执行快百万用户全表扫描只要几十毫秒。实际建模我们用了标签宽表一张user_tag表用户ID一列几百个标签各占一列ClickHouse 对这种宽表的压缩和扫描效率都远超行存数据库。运营端配置圈选条件后人群服务生成 SQL 提交过去再配合结果集的物化——把命中名单落到对象存储或者异步导出后续任务直接消费结果文件。有人会问为什么不用 Redis 做圈选Redis 适合高频KV查询比如“判断某个用户是否在某个群组”但不适合做多条件组合过滤。你总不能为了几十个标签组合提前把所有答案都存进 Redis——那种做法毫无扩展性。ClickHouse 和 Redis 在架构里的定位完全不同前者管分析型圈选后者管实时频控和状态查询。3.3 规则引擎与模型推理双轨可解释性要效果上限也要策略决策这块我的经验是不要试图用一个引擎搞定所有事。早期我们试着引入了一套重型规则引擎配置复杂、性能差、团队学习成本高用了一个月就被业务方嫌弃。后来换成了自研的轻量规则引擎条件表达式用脚本语言支持动作是调用固定的接口或模板配置中心统一管理版本发布。自研规则引擎的核心理由是营销规则在业务上很直白就是“如果某条件成立就执行某动作”几百条规则撑死了。这类需求用重型规则引擎属于杀鸡用牛刀反而增加维护成本。条件表达式用沙箱环境跑脚本注意一定要限制脚本的执行时间、内存和危险方法调用避免被恶意配置拖垮引擎。但纯规则引擎的上限不高。用户的个性化程度越来越高“同一类用户中哪些人更可能响应”这类问题靠人工写规则是不够的。所以我们单独部署了模型推理服务接的是 CTR 预估和流失概率模型策略编排器支持把模型打分和规则条件做组合。先规则过滤兜底再模型打分做精细决策既保住可解释性又拿到更好的效果。模型服务只暴露一个推理接口目录结构、模型版本、超时时间全都独立配置避免模型更新影响主链路。3.4 Redis 在频控与幂等中的关键角色Redis 在这套架构里不是缓存那么简单它承担着两个关键任务频控计数和幂等去重。频控这块的 key 设计是一个容易踩坑的细节。当时我们的第一版设计是user:{userId}:activity:{activityId}后来发现一个问题如果用户在多个活动里都加了同一个红包策略跨活动频控就失效了。最终设计改成user:{userId}:biztype:{bizType}:period:{timeWindow}先按业务维度聚合再在活动维度二次校验。具体实现上就是 Redis 的INCREXPIRE原子操作高效可靠。要注意的是Redis 频控计数过期时间要设置合理我们最初设计按小时重置结果大促活动从晚上8点持续到凌晨2点用户在不同小时段分别收到了多条营销消息。后来改成按自然日重置并支持业务方自定义窗口才算解决。幂等去重用的是 RedisSETNX命令key 类似event:dedup:{activityId}:{userId}:{scene}设置成功才允许继续处理避免 Kafka 消息重放时给同一用户重复发同一条消息。这里需要特别提醒幂等 key 一定要包含 scene 字段否则运营配置了两次相同活动幂等会误杀第二次的正常触达。4. 核心链路设计一次营销任务的完整旅程4.1 人群圈选链路从标签到候选集我们拿一个典型的批量营销场景走一遍全流程。运营在后台创建一个“双11大促唤沉睡用户”的活动圈选条件是“近60天未下单且近30天有登录”触达渠道是短信Push计划发送时间是明天10点。第一步后台把圈选请求发给人群服务。人群服务先生成一个圈选任务记录任务ID然后把条件翻译成 ClickHouse 查询。因为 ClickHouse 适合批量扫描但不适合承担高并发所以人群服务对圈选任务做了限流同一时间最多跑4个并发圈选其他请求排队。这个限制非常重要否则运营手一抖同时创建20个圈选ClickHouse 直接被打挂。第二步查询结果集先落到对象存储或 HDFS再导成一张人群快照表记录taskId userId的对应关系。快照一旦生成就不可变后续所有环节都依赖快照而不是重新查库更多人加到活动里就再跑一次圈选生成新快照。好处是后续调度、触达、效果统计全都基于同一份人群数据口径一致不会出现“调度认为发了5万人触达服务实际发了6万”这种数字对不上的问题。第三步快照生成后任务调度服务按预设的发送策略进行拆片。比如100万人分成20个分片每片5万人每片之间间隔5分钟。拆片的意义在于一次性把100万消息全部灌进触达服务通道扛不住分批释放可以把峰值拉平。拆片规则还会考虑用户活跃时段比如早上10点的Push这批人在线率高那前几个分片就优先包含高活跃用户。4.2 策略匹配与频控为什么顺序不能反批量链路的人群快照只是候选集真正决定“给哪些人发什么”的是策略匹配。这一步我特别想强调顺序问题必须先策略匹配再频控校验绝不能反过来。很多团队习惯先做频控再做策略结果一个用户明明没有被人群圈中却因为某个活动上的频控计数器被扣了次数导致真到该发时反而被频控拦截。这种问题排查起来非常隐蔽用户侧表现为“活动A不给我发活动B也不给我发”但日志里每个环节都是成功的。正确的顺序是这么设计的第一步用户ID同时进入人群服务和策略服务。人群服务判断用户是否命中圈选条件策略服务判断用户当前是否满足策略触发条件。第二步策略服务输出决策结果可能是一个具体模板ID也可能是“不触达”。第三步频控服务做三级校验。全局频控看这个用户今天已经被营销触达过几次活动频控看这个用户在本次活动下是否已经领过券通道频控看短信/Push通道的当日配额。第四步触达服务把模板ID和用户信息封装成消息投递到对应通道队列。实时链路的顺序也一样只是路径更短——用户行为事件进入 Kafka 后Flink 实时把用户ID推给决策引擎决策完成再走频控和触达。实时链路的核心指标是延迟我们的目标是 P99 在800ms以内其中模型推理就占了约200ms所以每一步都必须是轻量级的。4.3 触达通道管理限流、重试与回执触达服务是整套系统里最容易出事故的模块因为这里同时要面对供应商侧的各种不确定性。我们对接的每个通道都要实现同一套接口抽象发送接口、状态查询接口、回调处理接口。短信和 Push 的实际实现差异很大但对外表现必须一致这样上层业务不必关心用户选的是哪个渠道。通道限流必须做两层。一层是对接供应商的配额供应商说每秒最多500条短信我们就做500条的令牌桶超过就排队另一层是自己的熔断保护当某个通道的错误率超过阈值直接熔断不再往该通道投递新消息走降级策略比如从短信降级为站内信而不是硬扛到把供应商通道打崩。重试是必须的但重试最怕“重试风暴”。我们的经验是固定次数加指数退避最大重试次数是3退避时间分别是1分钟、5分钟、30分钟并加随机抖动防止大量消息同时重试对通道造成二次冲击。所有重试都通过独立的延迟队列控制不占用主链路线程。如果重试3次仍然失败消息进入死信表由人工或者对账脚本介入。消息回执是整个链路闭环的最后一环。触达服务发出消息后必须监听通道的回执更新消息状态表。这里最典型的坑是通道方说“已发送成功”但用户实际因为退订/黑名单根本没有收到。所以回执状态要分两层送达状态和实际阅读状态短信的送达状态来自运营商阅读状态只能靠用户点击行为反向推断Push 则直接有回执机制。4.4 数据回流效果分析的全链路埋点营销平台如果只有“发消息”没有“看效果”等于白做。我们的数据回流链路从消息创建时就开始了。每条消息创建时生成一个全局唯一的 messageId触达记录、回执记录、点击记录、转化记录全都带这个 messageId。具体流程是触达服务把发送明细写入 Kafka → Flink/Spark 做清洗和去重 → 写入 ClickHouse 分析表 → 报表服务做汇总。点击和转化埋点在 App 端通过统一的埋点平台上报再按 messageId 做关联。这里有个容易被忽视的细节短信的点击追踪通常走短链短链里必须带上 messageId 和用户ID 的参数否则你只知道这条短信被点击了不知道是谁点的、有没有下单整个效果分析就断链了。数据回流不只是做报表还要反哺策略。比如启动流失召回活动后看到“近60天未下单”用户中响应率和转化率都明显高于其他活动那说明这个圈选条件打得准可以把这个标签组合沉淀为核心人群包下次直接复用。这也是整套架构“智能”二字的体现——数据闭环让策略越用越聪明。5. 容量规划与压测百万用户不是静止的数字5.1 先算账每秒多少次决策、每天多少条消息架构设计不能拍脑袋第一步必须是算账。我们当时做了一个很简单的估算表这里可以直接分享给同样在规划营销平台的团队参考。假设注册用户100万日均活跃20万每天业务系统上报行为事件1200万条。那么平均每秒事件 QPS 1200万 / 86400秒 ≈ 139。按峰值是均值的20倍计算实时链路峰值约2800 QPS。再留2倍安全余量实时决策引擎至少得扛6000 QPS。模型推理平均耗时假设200ms单实例推理服务吞吐500 QPS6000 QPS 就需要约12个推理实例考虑高可用和波峰波动建议20个左右。假设一次大促要发100万条Push目标30分钟发完。Push供应商通道理论上能达到3000条每秒100万条需要约333秒也就是5分半钟。看起来30分钟绰绰有余但如果中间有失败重试失败率达到10%重试又要消耗更多时间实际是按分片逐步释放每个分片之间留缓冲。这套数字算完直接决定了我们集群规模的下限。我个人强烈建议所有容量规划必须基于业务侧的预估数据推导而不是按“未来可能增长10倍”去无限堆机器。否则资源预算过不了架构也会过度设计。5.2 压测中的真实瓶颈容量算完只是理论值到底行不行必须靠压测验证。我们第一轮压测就发现了几个意想不到的瓶颈脚本执行性能。规则引擎里的条件表达式用脚本语言实现最初版本没有做缓存每次执行条件判断都会做一次脚本编译CPU 直接被打满。后来加了脚本编译结果的缓存规则引擎的吞吐提升了近8倍。这个坑给我的教训是规则引擎的性能瓶颈通常在表达式解析而不是业务本身。ClickHouse 并发查询超时。人群服务限流后并发圈选控制在4个以内ClickHouse 单次查询毫秒级返回。但压测时发现当任务调度服务和实时链路同时访问人群服务的接口人群服务出现了排队超时。后来给人群服务加了单独的线程池隔离批量任务和实时任务各占一部分线程互不挤占。Redis 热 key 问题。大促期间同一个高频活动的前缀 key 访问量巨大单个 Redis 节点CPU告警。解决方案是给频控 key 做分片对用户ID取模分散到多个 key 上避免单 key 热点。5.3 弹性扩缩容的实战经验营销平台的流量特征就是脉冲式平时很平稳大促瞬间冲到峰值。所以弹性扩缩容不是锦上添花是救命稻草。我们的做法是消息队列积压数作为扩缩容的核心信号。Kafka 的消费积压一旦超过阈值消费者实例自动扩容积压恢复正常后延迟自动缩容。这个策略比单纯的按 CPU 扩缩容更贴合我们的场景因为营销任务本质上是“在规定时间内消化一批消息”。但要注意一个反向风险积压太大时一次性扩容过猛会直接打爆下游通道。所以我们给消费者设置了一个读速率上限不能超过通道配额的80%。比如短信通道每秒500条消费者每秒最多从队列里拉400条消息。这样即使积压再多也不会对下游产生伤害只是发送完成时间会延长。6. 稳定性与一致性营销平台最不能出错的地方6.1 幂等与去重发错消息是事故营销系统有一个和支付系统类似的要求消息不能多发更不能发错人。但基础设施天然不保证不重复Kafka 在极端情况下可能重放消息任务调度可能在超时后重复执行所以幂等是硬性需求。我们采用三层幂等设计。第一层是消息层的幂等Kafka 消息体里带上业务主键activityId userId scene templateId消费时 RedisSETNX校验已处理过就跳过。第二层是调度层的幂等离线任务执行前用数据库唯一索引或分布式锁防止并发重复调度。第三层是发送层的幂等对同一用户同一活动同一模板即使上层重复触达触达服务也只允许一次实际发送。有一件事要特别提醒重试一定要用独立消息ID不能复用原消息的幂等键。比如第一次发送超时状态未知你重试时如果复用原ID幂等层会以为已经处理过直接跳过结果用户一条都没收到。正确做法是重试生成新的消息ID同时保留“关联原消息ID”的字段方便链路追踪。6.2 削峰填谷与通道隔离“秒杀式”营销是最典型的流量形态。一个活动10点开抢10:00:00 瞬间大量请求进来如果所有请求直接打到触达服务和通道供应商肯定被拒。我们的削峰方案是按通道配额逐步释放消息进入触达队列后每个通道有一个固定的消费速率比如每秒200条多余的先积压着。这样看起来发送总量不变但发送曲线被拉平了对供应商最友好。通道隔离的设计同样关键。短信和Push是两个完全独立的通道绝不能共用一个线程池或者队列。短信通道抖动时如果线程池被短信重试任务占满Push的正常发送也会跟着卡住。我们把每个通道的消费线程池、队列长度、重试策略全部独立配置当某个通道熔断时其他通道完全不受影响。降级策略也要提前配好——短信通道熔断后可以自动降级为站内信或者Push保住触达率的同时不让用户彻底失去感知。6.3 全链路可观测性建设营销平台的可观测性我个人的经验是只有系统指标远远不够必须要有业务指标。系统指标看的是 CPU、内存、QPS、延迟而业务指标要看“当前任务已发送量”“成功率”“到达率”“点击率”“转化率”并且这些指标必须能按活动维度、人群维度、通道维度实时切片。我们接入全链路追踪后从一次任务创建开始就生成一个 traceId调度、圈选、触达、回执、分析各环节都打印这个 traceId。线上如果收到用户投诉“我没参与活动为什么给我发短信”运营可以拿着手机号找到对应的 messageId再反查完整的链路日志定位问题是出在人群圈选、策略匹配还是通道数据异常。没有这套链路追踪排查一个营销事故靠肉眼翻日志等于大海捞针。关键监控指标我用一张表理清楚建议团队直接抄作业指标说明告警阈值消息积压量队列积压积压时间超过10分钟发送成功率成功发送/总请求低于95%通道重试率重试消息/总消息高于5%决策延迟 P99实时链路超过1秒频控误杀率被频控拦截但实际应发任何异常即告警点击/转化回报全链路效果低于日常均值50%6.4 每日对账机制没有对账的营销系统等于裸奔营销系统还有一个和资金系统相似的地方必须对账。短信邮件这类通道是商业化服务发出去了是要扣钱的。通道方的账单基于他们的后台数据我们自己的后台数据可能因为回执延迟、网络抖动、消息重发等原因对不上。如果不对账月底账单出来发现多扣了几万块根本说不清。我们的对账机制是每天凌晨跑一次离线对账任务把当天的发送明细、回执明细按通道商分别拉出来和我们自己记录的状态做全量比对。差异部分输出报告由运营和财务去核对。差异率超过0.1%就必须排查原因是回执没更新还是通道商数据异常。这套机制上线后我们真的抓到了几次通道商回执丢失的问题也避免了不少资金损失。7. 踩坑实录与演进路线7.1 案例一延时消息导致重复触达这是上线早期出现的一个比较隐蔽的问题。当时我们为了做“加购15分钟未支付发券”这类延时触发场景用 Redis 的 zset 实现了一套简单的延时队列任务到期后由定时任务扫描并投递。某次线上故障导致进程重启重启后延时队列恢复的逻辑有缺陷同一批到期消息被重复投递了两次部分用户收到了两条一模一样的优惠券短信。完整排查链路是这样的先是用户投诉量暴增客服截图显示同一用户收到两条相同短信随后查触达记录表发现同一用户同一活动有两条 messageId但两笔记录的产生时间相差超过2小时进一步查消息日志发现两笔记录来自延时队列恢复任务第一次投递后进程崩溃恢复时没有记录“该任务已投递”的状态于是重复执行。根因清楚后我们做了两个修复一是延时队列必须和数据源绑定投递前做幂等校验二是消息投递本身严格依赖 RedisSETNX去重保证任何场景下都不能重复触达。7.2 案例二标签表全表扫描拖垮数据库这个问题前面已经提到过。事故当天的表现是运营在后台连续创建了几个圈选任务数据库 CPU 瞬间飙到100%核心业务接口全面超时。慢查询日志显示是几个WHERE条件极多、没有命中索引的大查询单条 SQL 执行了十几秒几乎把连接池全部占满。这个事故最大的教训是营销平台的数据库访问模式和分析系统的数据库访问模式是冲突的不能共存。MySQL 适合高频、短小、走索引的单行写入和查询而人群圈选是典型的低频、复杂过滤、扫描型查询。两者放在同一个库里注定互相伤害。这就是为什么要单独引入 ClickHouse 最本质的原因——数据访问模式决定存储选型而不是看哪款数据库流行。7.3 案例三通道重试风暴有一次短信通道供应商出现了短暂的临时性故障返回了“系统繁忙”的错误码。我们当时的重试逻辑是只要失败就立即重试而且没有限制并发。结果批量任务、实时任务同时命中重试逻辑短时间内向供应商发起了平时数倍的请求供应商的防护机制直接把我们账号临时封禁了。这次事故让我学到三个方面第一重试必须有退避和抖动否则会把下游系统本身当成了攻击目标第二重试必须和通道熔断联动连续失败达到阈值时应该自动暂停发送而不是继续重试第三重试的并发要控制批量任务的并发重试和实时任务的重试要分开计算不能共享资源池。现在我们的重试设计是指数退避1分钟、5分钟、30分钟加随机抖动最多3次通道连续失败率超过10%时自动熔断10分钟熔断后触发降级策略用户消息改为走站内信或者延迟到通道恢复后再发送。7.4 架构演进路线不是一步到位而是从问题中长出来最后聊聊这套架构是怎么演进到今天的。很多团队看完完整架构图会有压力觉得一次性搭这么大一套系统太难了。实际上我们的演进经历了四个阶段阶段一定时脚本扫库发短信。最朴素的做法只能支撑几万条消息没有任何实时能力。阶段二引入消息队列把发送异步化。解决的问题是“不要在业务进程里同步发短信卡住主流程”。阶段三加入实时计算支持行为触发。这一阶段才真正算得上“智能”因为能根据用户实时行为做决策了。阶段四平台化、可视化、OpenAPI。面向运营提供自助配置能力面向公司内部业务系统提供标准接口。演进的原则很简单每一阶段只解决当前最大的痛点不为可能永远不会发生的未来过度设计。如果你现在正在从阶段一向阶段二过渡我的建议是先只加消息队列做异步化不要一上来就上全套 Flink 和规则引擎。等业务真的需要了自然会有方向。再分享一点体会整个平台搭下来技术上最难的不是某个组件的选型而是把业务边界想清楚。人群服务到底要不要负责频控历史策略服务该不该直接调触达接口这些问题如果在一开始没有清晰答案后面每增加一个需求都会变成一次架构返工。如果现在让我重新做一次我会在项目启动的第一周就画好完整的业务闭环链路图把所有角色和依赖关系钉在墙上而不是先急着选技术栈。等到架构稳定了也不要停下来因为数据回流带来的策略优化潜力非常大——把每次活动沉淀下来的转化数据喂回人群圈选模型让模型的准确率自动提升才是“智能营销平台”真正的终局。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑