首页 / 资讯中心 / 文章详情

广告RTA实时决策:链路架构、容量估算与降级实践

广告RTA实时决策:链路架构、容量估算与降级实践 ★ FEATURED ARTICLE
1. RTA 在广告系统里到底站在哪个位置先把结论摆到最前面RTAReal-Time API是媒体侧在发起一次竞价之前向广告主实时发起的一次“要不要这个流量”的问询。广告主在几十毫秒内返回一个参与或不参与的决策媒体侧拿到这个决策之后再决定这次流量是否进入后面的排序和竞价环节。它不是出价不是创意也不是人群包它是竞价链路上游的一道实时闸门。我第一次接触这套机制是在一个投放成本压不下来的项目里。当时广告主手里有一份高价值用户名单和一份已经成交过的老客名单想把老客排除掉、把高价值用户捞出来加价但这两份名单每天都在变做成人群包推到媒体侧最短也要几个小时才能生效长的时候隔天。结果就是预算持续花在已经成交过的用户身上重复曝光ROI 一路往下掉。后来改成 RTA 接入媒体侧每次竞价前带着脱敏的设备标识来问一句我们这边查一次自己的库返回“这个不要”“这个要而且加价 1.2 倍”当天就把无效消耗砍掉了将近三成。所以这篇文章适合三类人看一是做广告系统后端、要对接媒体侧 RTA 接口的研发二是做投放策略、想知道 RTA 到底能解决什么、不能解决什么的优化同学三是数据侧、需要评估 RTA 接入后效果怎么衡量的人。无论你基础如何读完至少能搞清楚这条链路长什么样、参数怎么估、坑在哪里。1.1 先说清楚它和 RTB、DMP、人群包的区别这几个词经常被混着用实际职责完全不同。我用一个生活化的类比把广告投放想象成一场招聘会。DMP 和人群包是提前做好的简历库招聘方在开场前把候选人分好类贴好标签但标签是静态的今天贴完明天人可能就变了。RTB是现场竞价多个招聘方同时对一个人出价价高者得。RTA是在竞价开始之前招聘方先看一眼这个人的简历说“这个人我不面了”或者“这个人我很想要我愿意加预算”。三者的顺序是RTA 决策在前RTB 竞价在后。RTA 说不要后面的竞价根本不会发生这就省下了大量的无效竞价成本。维度RTARTBDMP/人群包DPA触发时机竞价前竞价中提前离线竞价中决策方广告主媒体侧撮合广告主/媒体媒体侧延迟要求极严通常几十毫秒严百毫秒级无较严数据流向媒体问、广告主答出价上报广告主上传商品库拉取典型用途老客排除、高价值加价、风控价格竞争定向圈选动态创意看懂这张表就明白了一个关键点RTA 的价值不在于“更精准”而在于“更实时 数据不出域”。人群包也能做精准但它必须把名单交出去而且更新慢RTA 是名单留在自己手里媒体只带着一个标识来问问完就走不留存。1.2 为什么静态定向包顶不住现在的投放节奏我在几个项目里反复验证过一个现象一个跑得不错的人群包生命周期通常在 7 到 14 天之间开始衰减。原因不复杂。第一是数据滞后。人群包从广告主侧导出、上传、媒体侧审核、生效链路走完少则几小时多则一天。对于电商大促、游戏开服、本地生活这类节奏极快的场景一天的滞后意味着策略完全失效。第二是粒度太粗。人群包本质上是布尔值——在或不在。但真实的业务判断往往是连续的“这个用户是我的高价值用户但今天已经买过了先别推”“这个用户是沉睡用户可以给一个高一点的出价试试”。这种带条件的、带权重的判断静态包表达不了。第三是数据合规压力。名单出域这件事本身就有风险涉及的字段越多、越敏感审核越难过。RTA 恰好绕开了这个问题媒体侧只发一个哈希后的标识过来广告主在本地完成匹配原始数据一步都没出门。注意RTA 返回的是“要不要”和“加不加价”它不参与最终出价计算。最终成交价仍然由媒体侧的竞价机制决定RTA 只影响你能不能进这个场、以及进场时的心理价位。1.3 三类最典型的接入场景不是所有投放都需要 RTA。我梳理下来真正值得投入研发资源接入的基本是这三类。第一类是新老客分层。广告主的成交数据在自己手里媒体侧不知道谁是老客。把老客排除掉把预算集中投给真正的新用户这是最刚需、收益也最直接场景。我见过最夸张的一个案子接入前有 40% 的曝光打在了已成交用户身上接入后这个数字降到 8% 以内。第二类是价值分层加价。同一个广告位对不同用户的期望价值差异可能有好几倍。RTA 支持返回一个出价系数广告主可以对自己模型打分高的用户返回一个大于 1 的系数对低价值用户返回小于 1 的系数甚至直接拒绝。这相当于把自己的一手数据变成了竞价优势。第三类是风控与反作弊。某些行业会遇到恶意点击、批量注册、羊毛党。这类流量从媒体侧的画像上看不出异常但广告主自己的风控库里有记录。用 RTA 在竞价前拦掉比事后申诉有效得多。2. 一次 RTA 请求里到底发生了什么理解这条链路最好的方式是拿一次真实的曝光请求顺着走一遍。用户在客户端刷新信息流媒体侧的广告服务开始处理这个请求。它先做基础的定向过滤把不符合条件地域、时段、频次的广告计划筛掉剩下一批候选计划。这时候凡是配置了 RTA 的计划媒体侧会把这些计划的 RTA 地址和当前流量的信息打包并发发出去。每个广告主各自的接口独立响应互不影响。回来的结果被合并成一张“准入表”只有返回“参与”的计划才进入后面的粗排、精排、竞价环节。整个 RTA 环节的耗时预算通常被压在30 到 80 毫秒之间部分媒体给的窗口更窄。这个数字很关键它决定后面所有的架构选择——为什么必须做缓存、为什么必须做超时降级、为什么不能在里面跑复杂查询。2.1 请求里都带了哪些字段不同媒体侧的字段定义不一样但核心结构高度一致。下面是一个典型的请求体字段名我做了抽象实际对接时以对方文档为准。{ request_id: a1b2c3d4-20240612-0001, timestamp: 1718150400123, ad_slot_id: feed_slot_03, media_user_id: 8f14e45fceea167a5a36dedd4bea2543, device: { os_type: android, os_version: 13, network: wifi, carrier: unknown }, context: { province: 310000, city: 310100, hour: 21, content_category: sports }, media_tags: [high_active, shopping_intent], plan_id: plan_88213, signature: xxxxx }几个字段值得单独说。media_user_id是媒体侧对用户的稳定标识通常是经过哈希或加密的广告主拿到后要用同样的算法做一次映射才能和自己库里的用户对上。这里有个坑不同媒体的 ID 体系完全不通同一个用户在不同平台上的标识毫无关系。所以广告主侧一般要维护一张映射表把各家的 ID 映射到自己的统一 ID 上。request_id是排障的生命线。线上出问题时拿着这个 ID 能一路追到日志没有它基本没法定位。media_tags是媒体侧附带的标签通常是粗粒度的活跃度、兴趣分类。这些标签能用但不要过度依赖一是粒度粗二是各家定义不统一。2.2 返回值的语义比想象中更微妙最简化的返回就是一个布尔值参与或不参与。但实际对接中返回值往往包含更多信息。{ request_id: a1b2c3d4-20240612-0001, code: 0, participate: true, bid_ratio: 1.25, custom_creative_id: creative_778, ttl: 300 }bid_ratio是出价系数媒体侧会用这个系数去调整这个计划在本流量上的出价。系数怎么定后面第 4 节会讲。ttl是缓存有效期单位秒。这个字段很多人忽略但它对降低 QPS 峰值极其重要。返回 300 意味着同一个用户在这 5 分钟内的后续请求媒体侧可以直接复用这次的结论不必再打过来。合理设置 ttl能把实际请求量压掉一大半。提示bid_ratio不是越大越好。我给的建议是新接入的广告主先只返回 0 或 1跑一周把链路跑稳再加系数逻辑。一开始就上复杂的系数策略出问题的时候你分不清是链路问题还是策略问题。2.3 超时了怎么办降级策略决定成败这是整个 RTA 设计里最容易被低估的部分。广告主的服务不可能永远健康网络抖动、发布重启、下游存储慢查询任何一环出问题都会导致超时。超时之后怎么处理必须在接入前就和业务方定死。两种降级方向各有代价降级策略含义好处风险默认参与超时当作“要”不丢量投放不断可能大量无效消耗成本失控默认不参与超时当作“不要”成本可控丢量量级断崖式下跌怎么选我的经验是按投放目标分。以成本为第一目标的计划用默认不参与宁可少投也别乱投以起量为第一目标的计划用默认参与先保住曝光事后通过报表把无效流量找出来。两种策略可以按计划配置不必全站一刀切。另外还有一个更细的做法按超时类型区分。如果是连接超时网络层问题大概率是整体故障走默认不参与如果是读超时对方处理慢说明服务活着只是慢可以走默认参与。这个区分需要媒体侧支持不是所有平台都给但对成本控制很有价值。3. 架构选型自建、托管还是标准化方案确定要接 RTA 之后第一个决策是这套服务怎么落地。我见过三种做法各有适用面。3.1 三种部署形态的实际差异自建独立服务自己写接口、自己部署、自己扛容量。优点是完全可控想加什么逻辑就加什么逻辑用户数据完全在自己机房缺点是从零搭高可用架构人力投入不小一台机器扛不住的时候扩容、限流、监控全都要自己做。托管在云上的函数服务把决策逻辑写成一个函数流量来了自动弹性扩容。上手快前期几乎没有运维负担但冷启动延迟是个隐患第一次调用可能要几百毫秒这在 80 毫秒的窗口里是致命的。要么做预热要么用预留实例成本会上去。用媒体侧提供的标准化接入工具部分平台会提供一套标准化的对接组件广告主只需要填充决策逻辑。接入成本最低但灵活度受限复杂的多条件判断、多数据源联查往往表达不了。形态上手成本延迟可控性灵活性适合规模自建服务高高高中大型日均请求千万级以上云函数低中需预热中中小型波峰波谷差异大标准化组件很低高低验证阶段、简单规则我的建议是分两步走先用标准化组件或最小自建版本跑通链路验证业务价值再决定要不要投入做完整的高可用架构。很多人一上来就设计一套复杂的微服务结果业务侧发现收益不及预期架构白做了。3.2 容量怎么估一步一步算给你看这一步最容易被拍脑袋。我见过有人按“日活乘个系数”就报了机器数上线当天直接被打挂。正确的做法是从曝光量往回推。假设某广告主日均曝光 1 亿次媒体侧的平均竞价倍率是 3也就是一次曝光背后平均发生 3 次竞价请求RTA 挂在计划层级平均每个请求要问 1.5 个 RTA 接口那么日均 RTA 请求数 1亿 × 3 × 1.5 4.5 亿次 平均 QPS 4.5亿 / 86400 ≈ 5208再看峰值。信息流类流量的峰谷比通常在 3 到 5 之间晚间高峰集中。取 4峰值 QPS ≈ 5208 × 4 ≈ 20800单机处理能力取决于你的实现。一次请求如果只是查一次本地缓存加一次 Redis单核轻松跑到 2000 QPS 以上如果每次都要查一次数据库单机可能只有 300 到 500 QPS。按查 Redis 估算单机 8 核保守取 8000 QPS所需机器数 20800 / 8000 ≈ 2.6 → 取 4 台留 50% 余量这套算法比拍脑袋靠谱得多。但要注意两个修正项一是 TTL 缓存会显著降低实际请求量上面算的是理论上限二是突发流量比如某个内容突然爆了瞬时 QPS 可能是均值的十倍以上这时候靠限流和降级兜底不能靠堆机器。3.3 高可用设计三个必须做的动作第一多可用区部署。单机房的风险不用多说一次网络割接就能让投放停半天。至少两个可用区媒体侧配置多个回调地址做容灾切换。第二限流要在入口做。不要指望下游扛住所有流量。在接口层设置令牌桶超过阈值的请求直接走降级返回把压力挡在业务逻辑之外。限流阈值按压测结果的 70% 设定留出缓冲。第三监控要盯住延迟分位数而不是均值。均值 20 毫秒看着很健康但 P99 可能是 300 毫秒意味着每 100 次请求就有 1 次超时。RTA 这种场景P99 才是真实体验。我一般会同时看 P50、P95、P99 和超时率四条线。4. 从联调到稳定运行完整的落地流程链路设计和容量都定了接下来是实操。这一段我按时间顺序讲把每个阶段的动作和容易踩的坑都说清楚。4.1 接口契约必须白纸黑字写下来对接之前一定要和媒体侧把这几件事写成文档口头约定不作数。超时时间明确写死比如 60 毫秒。不要用“尽快”这种描述。返回码约定0 表示成功非 0 的每一种都对应什么含义媒体侧收到非 0 时怎么处理全部列清楚。我遇到过返回码没定义全广告主返回了 1媒体侧当成“不参与”导致全天零曝光的事故。幂等性同一个request_id重复请求返回值必须一致。这个在媒体侧重试时很关键。鉴权方式签名算法、密钥轮换周期。密钥一定要支持双活否则轮换那一刻全部请求失败。字段兼容新增字段不能影响老逻辑缺失字段要有默认值。这条特别重要媒体侧随时可能加字段。# 一个极简的决策函数骨架重点看异常处理 def decide(req): try: uid map_to_internal_id(req.get(media_user_id)) if not uid: return {code: 0, participate: True, bid_ratio: 1.0} profile cache.get(uid) if profile is None: profile load_profile(uid) # 兜底查库需限流 cache.set(uid, profile, ttl300) if profile.is_purchased_recently: return {code: 0, participate: False} if profile.value_score 0.8: return {code: 0, participate: True, bid_ratio: 1.2} return {code: 0, participate: True, bid_ratio: 1.0} except Exception: # 任何异常都不能抛出去必须返回一个合法结构 return {code: 0, participate: True, bid_ratio: 1.0}这段代码里有两个细节值得强调。map_to_internal_id查不到时返回参与而不是拒绝因为 ID 映射覆盖率很难做到 100%全拒会丢大量正常流量。最外层的except必须兜住任何未捕获的异常导致返回体不合法媒体侧可能直接判定整批请求失败。4.2 联调阶段要做的四件事造流量。媒体侧一般会提供测试流量或模拟请求工具。如果没有就自己按字段定义拼请求覆盖正常用户、未知用户、缺失字段、超长字段、非法编码这几种情况。压测。按估算峰值的 1.5 倍压观察 P99 和错误率。压测时特别注意下游依赖缓存和数据库的连接池很容易先被打满。对账。把媒体侧记录的请求量和自己侧记录的请求量做比对差异超过 1% 就要查。常见原因是重试和缓存命中导致两边计数口径不一致。灰度。这一条单独展开说。4.3 灰度节奏宁可慢一点我给的建议节奏是 1%、5%、20%、50%、全量每一档至少观察 24 小时看四个指标请求量是否符合预期、超时率、参与率、以及投放侧的消耗和转化变化。任何一档出现异常就回退不要硬扛。有一回我们在 5% 那档发现参与率高达 96%看着像是好事仔细一查是 ID 映射覆盖率只有 4%剩下 96% 全都走到了“查不到就放行”的默认分支等于 RTA 根本没生效。如果不是分档灰度这个问题会一直藏着。注意灰度期间不要同时改投放策略和 RTA 逻辑。两个变量一起动出问题无法归因。这是我在项目里吃过亏的地方一次改动引入两个变化排查花了整整两天。5. 效果怎么衡量指标体系和常见误判RTA 上线之后最难的不是技术是回答“它到底有没有用”。这一节讲指标怎么设以及几个很容易踩的统计陷阱。5.1 该盯的核心指标技术侧和业务侧的指标要分开看。技术侧四个请求量、P99 延迟、超时率、参与率。参与率的绝对值没有意义要看它的变化趋势和分群差异。如果某个用户群参与率突然从 80% 掉到 20%那一定是数据源出了问题。业务侧三个无效曝光占比、转化成本、增量 ROI。前两个直接看报表第三个需要做实验。增量 ROI 的计算一定要用对照组。方法是在同一批计划里随机切出 5% 到 10% 的流量不接 RTA作为对照跑够统计显著所需的天数再比。我见过太多人直接拿接入前后的数据对比得出一个漂亮但完全错误的结论。5.2 三个典型的统计陷阱陷阱一前后对比代替 AB。大促期间接入 RTA前后数据变化里混着大盘流量质量的变化、竞品出价的变化。不做对照无法分离出 RTA 的真实贡献。陷阱二过滤带来的幸存者偏差。RTA 把一部分流量挡掉了剩下的流量本身质量就更高转化率自然上升。但这不代表整体收益上升因为被挡掉的那部分流量本来也可能有转化。所以一定要看总转化数和总成本而不是只看转化率。陷阱三归因窗口不一致。RTA 影响的是曝光转化可能在几天后才发生。如果实验只跑三天就下结论会严重低估效果。窗口至少要覆盖完整归因周期。指标观察周期判断标准超时率实时超过 1% 立即排查参与率按日环比波动超过 20% 要查原因无效曝光占比按周下降即为正收益增量 ROI完整归因周期实验组显著高于对照组6. 线上问题排查实录真正上线之后问题五花八门。我把遇到过的、以及同行反馈过的典型情况整理成一张速查表后面再展开讲两个最有代表性的。6.1 常见问题速查表现象最可能原因排查动作曝光量断崖下跌参与率异常低或返回结构不合法查返回码分布、抽样看返回体成本不降反升bid_ratio 设置过高或降级走了默认参与查系数分布、查超时率超时率飙升下游慢查询、缓存击穿、发布重启看 P99、看缓存命中率、看发布时间点参与率接近 100%ID 映射失效全走默认分支统计映射命中率不同媒体效果差异大各媒体 ID 覆盖率不同或流量结构不同分媒体拆指标请求量与预估差十倍TTL 缓存生效或计划未全量开启核对 ttl 设置、核对计划配置6.2 两个真实排查过程第一个曝光量莫名其妙掉了四成。早上九点开始投放侧的曝光量曲线直接砍掉一半没有任何代码发布。先看 RTA 侧请求量正常超时率 0.3% 正常但参与率从 78% 掉到了 21%。这就锁定了问题在决策逻辑。再往下查发现参与率下跌集中在某几个城市。继续拆发现是 ID 映射服务的一个分片挂了那个分片覆盖的用户全部映射失败走到了我前面代码里那个“查不到就放行”的分支。但等等放行应该是参与率上升啊再一看不是。这里有个细节映射失败之后会去查兜底存储而兜底存储在故障时返回的是一个空对象而不是 None代码里判断profile is None不成立继续往下走读到了一个所有字段都是默认值的 profileis_purchased_recently默认是 True于是全部拒绝。这就是默认值设计不当的典型事故。修法很简单把默认值改成明确的“未知”状态未知就走放行。第二个成本控制住了但转化数跌得比预期多。接入 RTA 两周成本降了 35%但总转化数降了 28%增量 ROI 反而是负的。这个结果说明过滤得太狠了。拆开看被拒绝的流量里有一批是“近 30 天有浏览但未购买”的用户我们当时的规则是只要有浏览记录就拒绝理由是“已经看过广告了”。但数据显示这批人里有一部分是在做决策比较属于高意向人群直接拒掉是浪费。调整方案是把布尔判断改成系数判断有过浏览但未购买的返回 0.8 的系数而不是直接拒绝已经购买的才返回拒绝。改完之后成本回升了 8%但总转化数回升了 22%整体 ROI 转正。这个教训我一直记着RTA 的拒绝是最重的手段能不用就不用能用系数解决的优先用系数。布尔值适合表达“绝对不要”而绝大多数业务判断其实是程度问题不是是非问题。6.3 几个实操心得接口里千万别放同步的数据库查询。哪怕加了索引一次慢查询就能把整个接口的 P99 拖崩。所有需要查库的判断提前算好放进缓存。返回体一定要做长度控制。有的媒体侧对响应体大小有限制超了直接判失败。自定义创意 ID 这类字段能省就省。日志要打全但要注意脱敏。request_id、参与结果、命中规则三条必打用户标识做哈希处理。出问题的时候这三条能覆盖八成排查场景。压测要用真实流量结构。拿一批均匀分布的假请求压出来的数据毫无意义真实流量的特征是极度不均匀少数用户贡献了大部分请求缓存命中率天差地别。我个人在几个项目里反复验证的一点是RTA 上线初期最容易出问题的不是决策逻辑本身而是那些没被明确定义的边界情况——ID 查不到、字段缺失、下游超时、返回体格式不对。把这些边界一个一个列出来逐个定义好行为比优化决策算法本身带来的收益大得多。后面再扩展的话可以考虑把决策逻辑做成可配置的规则引擎让投放同学自己能调不用每次改规则都排一次研发排期这个在节奏快的行业里价值很高。
阅读完成 · 觉得有帮助?
咨询建站