广告投放系统
广告投放系统:媒体竞价、品牌合约、程序化交易、搜索广告与广告主买量
广告领域的"投放系统"至少有五种:腾讯广告(AMS)、巨量引擎让广告主花钱买媒体流量;品牌合约系统替品牌广告主保量; DSP 在 ADX 上对每一次曝光实时出价;搜索和电商平台按关键词卖流量;游戏、电商、金融公司自建的买量平台帮投手在几十个媒体上 批量建计划、盯 ROI。五者都涉及"花钱买流量"或"卖流量赚钱",但谁拥有流量、谁做决策、优化什么目标不同,架构也因此不同。
站内自有流量(运营位、触达、权益发放)的投放系统见 运营位投放系统.md。
本文中的延迟、QPS、候选数量等数字,除特别说明外均为示意量级,不代表任何一家平台的实际配置; 各平台的产品层级、计费和赔付规则属于实现细节,可能随版本变化。
1. 共同抽象
所有广告投放系统都在回答同一个问题:在某个位置,给某个人,在某个时间,按某种规则,展示某条广告。
| 维度 | 含义 | 广告系统里的叫法 |
|---|---|---|
| WHERE | 流量出现在哪 | 广告位 / 版位(placement)、搜索关键词 |
| WHO | 给谁看 | 定向(targeting)、人群包、Lookalike |
| WHEN | 什么时候看 | 投放时段、排期、预算节奏(pacing) |
| HOW | 按什么规则 | 出价、计费方式、频控、预算 |
| WHAT | 看到什么 | 创意(素材 + 落地页) |
同一个位置、同一个人有多个候选广告时,谁赢由决策方式决定:
| 决策方式 | 谁赢 | 典型系统 |
|---|---|---|
| 竞价 | eCPM(期望千次展示收入)最高的 | 竞价广告(腾讯广告、巨量引擎、Google Ads) |
| 合约分配 | 按合同保量,系统在合约之间分配流量 | 品牌 GD(Guaranteed Delivery)广告 |
| 实时竞价 | ADX 上多个 DSP 出价最高的 | 程序化交易(RTB) |
| 关键词竞价 | 关键词相关且"出价 × 质量"最高的 | 搜索广告、电商搜索广告 |
竞价型系统的核心模块是检索、预估、竞价、计费、预算控制;合约型系统的核心模块是库存预估和流量分配; 广告主侧系统的核心模块是媒体 API 编排、归因和回传。
1.1 计费方式速查
| 计费方式 | 广告主按什么付钱 | 风险在谁 | 典型场景 |
|---|---|---|---|
| CPT | 按时长(包一天开屏) | 广告主 | 品牌 |
| CPM | 按千次曝光 | 广告主 | 品牌、GD |
| CPC | 按点击 | 平台承担 CTR 风险 | 搜索、效果广告 |
| CPA | 按转化 | 平台承担 CTR + CVR 风险 | 很少直接使用 |
| oCPC / oCPM / oCPA(统称 oCPX) | 广告主按转化出价,平台按点击或曝光扣费 | 平台负责预估,成本偏离时按规则赔付 | 目前效果广告的主流 |
oCPX 的"o"是 optimized:广告主表达的是"我愿意为一个转化付多少钱",平台用 pCTR、pCVR 把它换算成每次曝光的出价。
2. 领域全景
flowchart LR
subgraph 买方["买方(出钱买流量)"]
ADV[广告主]
ADVSYS[广告主自建投放平台<br/>买量工具]
DSP[DSP<br/>需求方平台]
AGENCY[代理商 / 服务商]
end
subgraph 交易["交易撮合"]
ADX[ADX 广告交易平台]
end
subgraph 卖方["卖方(拥有流量)"]
MEDIA_AD[媒体自有广告平台<br/>腾讯广告 / 巨量引擎 / Google Ads]
SSP[SSP 供应方平台]
PUB[媒体 / App 流量]
end
ADV --> ADVSYS
ADVSYS -- Marketing API --> MEDIA_AD
ADVSYS -- RTA 实时接口 --> MEDIA_AD
ADV --> AGENCY --> MEDIA_AD
DSP -- OpenRTB --> ADX
ADX --> SSP --> PUB
MEDIA_AD --> PUB
MEDIA_AD -- 联盟流量 --> PUB
| # | 领域 | 典型系统 | 谁在用 | 目的 | 流量归属 | 决策方式 |
|---|---|---|---|---|---|---|
| 1 | 媒体侧竞价广告平台 | 腾讯广告(AMS)、巨量引擎、百度凤巢、Google Ads、Meta Ads | 广告主、代理商 | 媒体把流量变现,广告主按效果买量 | 媒体 | 竞价 |
| 2 | 品牌合约广告(GD) | 各媒体的开屏、合约保量产品 | 品牌广告主 | 在指定时段、指定人群上保证曝光量 | 媒体 | 合约分配 |
| 3 | 程序化交易(RTB) | DSP、SSP、ADX(Google AdX、腾讯 ADX) | DSP 代表广告主;SSP 代表媒体 | 跨媒体实时竞价,一次曝光一次交易 | 第三方媒体 | 实时竞价 |
| 4 | 搜索 / 电商搜索广告 | 百度凤巢、Google Search Ads、淘宝直通车、京东京准通、Amazon Sponsored Products | 广告主、商家 | 在用户表达明确意图(搜索词)时展示广告 | 搜索引擎、电商平台 | 关键词竞价(和自然结果混排) |
| 5 | 广告主侧买量平台 | 游戏 / 电商 / 金融公司自建投放平台;第三方工具 | 投手、增长团队 | 在多个媒体上批量建计划、调价、控成本、算 ROI | 不拥有流量,调用媒体 API | 投手规则 + 自动化策略 + RTA |
媒体自有广告平台通常还有"联盟"业务(腾讯广告的优量汇、巨量引擎的穿山甲):把自己的广告主需求投到第三方 App 上, 第三方 App 通过 SDK 接入,架构上是第 3 节的竞价引擎加上第 5 节 SSP 的角色。
3. 媒体侧竞价广告系统(腾讯广告、巨量引擎)
3.1 目的
媒体(微信、QQ、腾讯新闻、抖音)有流量,广告主有预算。广告平台的目标是:
- 对媒体:最大化流量变现收入(eCPM)
- 对广告主:在出价和预算约束内拿到尽量多的转化
- 对用户:广告体验不能太差(频控、相关性、质量分、广告负载)
三者之间存在冲突:广告多插几条收入上涨,用户时长下降。平台最终优化的是长期总价值, 广告负载率和用户体验约束就是用来平衡这个冲突的。
3.2 系统全景
flowchart TB
subgraph 投放端["投放端(写路径,秒级到分钟级)"]
UI[投放管理平台 Web]
MAPI[Marketing API]
ADSVR[物料服务<br/>账户 / 计划 / 广告 / 创意 CRUD]
AUDIT[审核系统<br/>机审 + 人审]
ADDB[(物料库 MySQL 分库分表)]
ACCT[(账户资金库)]
DMP[DMP<br/>人群包、标签、Lookalike]
UI & MAPI --> ADSVR --> ADDB
ADDB --> AUDIT --> ADDB
end
subgraph 索引["索引构建与分发"]
STATE[有效性计算<br/>各层状态 ∧ 审核 ∧ 余额 ∧ 排期]
BUILD[索引构建<br/>正排 + 倒排]
DIST[索引分发<br/>全量快照 + 增量流]
ADDB -- binlog --> STATE --> BUILD --> DIST
ACCT --> STATE
end
subgraph 在线["在线投放引擎(读路径,几十毫秒)"]
MIXER[接入 / 编排层]
FEAT[用户特征服务]
RETR[召回服务<br/>多分片]
PRED[预估服务<br/>粗排 / 精排]
AUCTION[竞价与策略]
CTRL[实时状态<br/>预算 / 频控 / pacing]
MIXER --> FEAT
MIXER --> RETR --> PRED --> AUCTION
CTRL --> RETR
CTRL --> AUCTION
end
subgraph 数据["日志、计费与训练"]
TRACK[监测服务<br/>曝光 / 点击]
CONV[转化接入<br/>回传 API / SDK / 落地页]
STREAM[实时流<br/>Kafka + Flink]
ANTI[反作弊]
BILL[计费]
REPORT[报表 OLAP]
SAMPLE[样本拼接]
TRAIN[模型训练]
TRACK --> STREAM --> ANTI --> BILL
CONV --> STREAM
BILL --> ACCT
BILL --> CTRL
STREAM --> REPORT
STREAM --> SAMPLE --> TRAIN
end
DIST --> RETR
DMP --> FEAT
TRAIN --> PRED
AUCTION --> TRACK
读写分离是广告系统最基本的结构:投放端是低 QPS、强一致的配置系统;在线引擎是高 QPS、 低延迟的只读检索系统,两者之间用索引连接;计费和训练依赖日志流把在线结果带回来,形成闭环。
3.3 投放端:物料模型与审核
物料层级
广告主在投放端配置的是一个树形结构。各家叫法不同,并且随产品版本调整:
账户(Account)── 余额、资质、日预算
└── 推广计划 / 项目(Campaign)── 推广目标(应用下载、表单、商品购买)、总预算
└── 广告组 / 广告(Ad Group)── 定向、版位、出价方式、出价、排期、日预算
└── 创意(Creative)── 图片 / 视频 / 文案、落地页、监测链接
定向和出价挂在广告组上,素材挂在创意上。一个广告组下多个创意由系统做创意优选。 近几年各平台的趋势是减少层级、把定向和出价交给系统自动化(自动版位、智能定向、自动出价), 广告主只填推广目标、预算和素材。
核心表(示意)
| 表 | 主要字段 |
|---|---|
account |
账户 ID、主体资质、行业、余额、日预算、状态 |
campaign |
计划 ID、账户 ID、推广目标、总预算 / 日预算、状态 |
adgroup |
广告 ID、计划 ID、版位、定向(JSON 或关联定向表)、出价方式、出价、目标转化、排期、状态 |
creative |
创意 ID、广告 ID、素材 ID 列表、文案、落地页、监测链接、审核状态 |
material |
素材 ID、类型、URL、MD5、尺寸、时长、审核结论 |
targeting |
定向包 ID、年龄 / 性别 / 地域 / 兴趣 / 人群包(定向 / 排除) |
物料库按账户 ID 分库分表:同一账户下的物料总在一个分片上,投放端的查询和批量操作都按账户进行。
有效性:一条广告什么时候能投
在线引擎只关心"这条广告现在能不能投"。这个布尔值由多层状态共同决定:
可投 = 账户有效 ∧ 账户余额 > 0 ∧ 账户日预算未耗尽
∧ 计划开启 ∧ 计划预算未耗尽
∧ 广告开启 ∧ 当前时间在排期内 ∧ 广告日预算未耗尽
∧ 至少一个创意审核通过
这些状态变化来源不同:广告主操作(开关、改预算)、审核结论、计费扣款(余额、预算)、时间推移(排期)。 有效性计算服务订阅所有变化源,算出"可投广告集合"的增量变化,再交给索引构建。 预算和余额类状态变化最频繁,通常不进索引,由在线引擎在过滤阶段查实时状态服务(第 3.9 节)。
审核
| 环节 | 做法 |
|---|---|
| 资质审核 | 账户开户时审主体资质、行业资质(金融、医疗、教育有特殊资质要求) |
| 机审 | 图片 OCR、图像分类(涉黄、涉政、违禁品)、文案敏感词、视频抽帧、落地页抓取检测;相同 MD5 素材复用历史结论 |
| 人审 | 机审不确定或高风险行业进入人审队列;按行业、优先级、SLA 分配 |
| 巡检 | 通过审核的广告投放后仍会抽检,落地页可能在审核后被篡改 |
审核决定新广告多久能上线,是投放端的主要时延来源。
3.4 索引构建与分发
在线引擎需要两类索引:
| 索引 | 结构 | 用途 |
|---|---|---|
| 正排 | 广告 ID → 广告属性(出价、出价方式、目标转化、创意列表、所属账户 / 计划) |
召回之后取广告详情 |
| 倒排 | 定向条件 → 广告 ID 列表 |
召回:根据用户属性找满足定向的广告 |
更新方式是全量 + 增量:
flowchart LR
DB[(物料库)] -- 定时导出 --> FULL[全量构建<br/>生成带版本号的快照]
DB -- binlog --> KAFKA[增量消息]
FULL --> STORE[(快照存储)]
STORE -- 拉取 --> NODE[在线召回节点]
KAFKA -- 订阅 --> NODE
NODE --> MEM[内存索引<br/>双 buffer 切换]
- 在线节点启动时加载最新全量快照,再从快照对应的消息位点开始消费增量
- 全量快照定期重建,避免增量长期累积导致的内存碎片和数据漂移
- 加载新全量时构建第二份内存索引,构建完成后原子切换指针(双 buffer),旧索引等在途请求结束后释放
- 增量更新要求秒级生效:广告主暂停广告后,如果在线引擎几分钟后才停止投放,就会出现无法计费或超投
索引规模超过单机内存时按广告分片:每个召回分片持有一部分广告,编排层并行请求所有分片再合并。 分片数和副本数分别由索引规模和 QPS 决定。
3.5 在线链路
sequenceDiagram
participant C as 客户端
participant M as 编排层
participant F as 用户特征
participant R as 召回分片 × N
participant P as 预估服务
participant S as 状态服务
participant A as 竞价与策略
C->>M: 广告请求(用户 ID、版位、上下文)
M->>F: 取用户画像 + 实时行为特征
M->>R: 并行召回(带用户属性)
R->>S: 过滤预算 / 频控
R-->>M: 各分片候选
M->>P: 粗排
M->>P: 精排(pCTR、pCVR)
M->>A: eCPM 排序、出价调节、计费价、混排
A-->>M: 最终广告
M-->>C: 广告 + 监测链接(带 trace_id)
M->>M: 异步记录请求日志、特征快照
| 阶段 | 输入 → 输出量级 | 做什么 | 关键技术 |
|---|---|---|---|
| 召回 | 百万级广告 → 万级 | 找出定向条件和当前用户匹配的广告 | 布尔表达式倒排索引、向量召回 |
| 过滤 | 万级 → 千级 | 预算耗尽、超频、不在排期、黑名单、已安装 / 已转化排除 | 内存状态表、实时计数 |
| 粗排 | 千级 → 百级 | 轻量模型快速打分 | 双塔模型 |
| 精排 | 百级 → 十级 | 预估 pCTR、pCVR | 深度 CTR / CVR 模型 |
| 竞价 | 十级 → 1~数条 | 按 eCPM 排序,算计费价 | GSP、oCPX 出价调节 |
| 混排 | 广告 + 自然内容 | 决定广告插在信息流第几位 | 广告负载率约束、用户体验分 |
延迟预算(示意):
| 环节 | 耗时量级 |
|---|---|
| 特征获取 | 几毫秒 |
| 召回(并行扇出 + 合并) | 十毫秒左右 |
| 粗排 | 几毫秒 |
| 精排(GPU / CPU 推理) | 十几到几十毫秒 |
| 竞价、策略、混排 | 几毫秒 |
| 端到端 | 几十毫秒到一百毫秒左右 |
所有下游调用都有超时:召回某分片超时就丢弃该分片结果,精排超时就退化用粗排分数。 广告请求宁可少出一条广告,也不能拖慢媒体页面。
3.6 召回
定向倒排:布尔表达式索引
广告的定向是一个布尔表达式,例如:
age ∈ {18-24, 25-34} AND gender = 女 AND region ∈ {北京, 上海} AND NOT 人群包 = 已安装用户
请求是一组属性赋值(age=25-34, gender=女, region=上海, ...)。问题是"找出所有被这组赋值满足的表达式",
这是反向检索:普通搜索是用查询找文档,这里是用文档(用户)找查询(广告定向)。
逐条遍历百万广告不可行。业界通用做法是 Yahoo 在 VLDB 2009 发表的 Indexing Boolean Expressions 提出的 Conjunction 索引:
- 把每个定向表达式转成析取范式(DNF),每个合取子句(Conjunction)单独索引
- 按合取子句里
∈条件的个数 K 分组,建两级倒排:K → (属性=值) → 合取子句列表 - 查询时对每个 K,只有当某个合取子句在至少 K 个倒排链上都出现时才命中,可用类似多路归并的方式跳跃求交
NOT条件和 K=0(无正向条件)的子句单独处理
一个具体例子:
广告 A:age ∈ {25-34} AND gender ∈ {女} → 合取子句 c1,K=2
广告 B:region ∈ {上海} → 合取子句 c2,K=1
广告 C:age ∈ {25-34} AND region ∈ {北京} → 合取子句 c3,K=2
倒排(K=2):age=25-34 → [c1, c3] gender=女 → [c1] region=北京 → [c3]
倒排(K=1):region=上海 → [c2]
请求:age=25-34, gender=女, region=上海
K=2:c1 出现在 age=25-34 和 gender=女 两条链上,命中;c3 只出现 1 次,不命中
K=1:c2 出现 1 次,命中
结果:{A, B}
地域、年龄这类维度取值有限,可以直接作为倒排的 key;兴趣标签、人群包这类维度取值多、用户属性多, 通常召回后再用用户侧的"人群包列表"做过滤,或把人群包 ID 当成用户的一个属性值进倒排。
模型召回
定向越来越宽(智能定向、不限定向)后,满足定向的广告可能有几十万条,靠定向倒排已经筛不下来。 需要按"这个用户可能对哪些广告感兴趣"召回:
| 召回通道 | 做法 |
|---|---|
| 向量召回 | 双塔模型分别算用户向量和广告向量,用 ANN(HNSW、Faiss IVF-PQ)取内积最高的 Top K |
| 行为召回 | 用户近期点击 / 转化过的广告主、商品、类目的相似广告 |
| 重定向 | 访问过广告主落地页、加购未下单的用户,召回该广告主的广告 |
| 冷启动通道 | 新广告单独保留一定召回配额,保证能拿到探索曝光 |
多个通道各自召回,合并去重后进入过滤和粗排。每个通道的配额是调参对象。
3.7 预估
模型
| 模型 | 用途 | 代表方法 |
|---|---|---|
| 粗排 | 千级候选快速打分 | 双塔(用户塔、广告塔分开计算,广告向量可离线预算) |
| pCTR | 点击率 | Wide & Deep、DeepFM、DIN(用注意力建模用户历史行为) |
| pCVR | 点击后转化率 | ESMM(在全曝光空间联合建模 CTR 和 CTCVR,缓解只用点击样本训练带来的样本选择偏差) |
| 深度转化 | 付费、留存、LTV | 多任务模型,目标是次日留存、首日付费金额等 |
预估要准,不只是排序要对
推荐系统只要排序对就行;广告的 eCPM 直接用 pCTR × pCVR 的绝对值乘出价,预估偏高 20%, 广告主就多付 20% 的钱。因此广告预估比推荐多几件事:
| 问题 | 做法 |
|---|---|
| 校准(calibration) | 负采样、样本分布变化都会让模型输出偏离真实概率;上线前后用分桶统计把预估值映射回真实转化率(Platt scaling、保序回归) |
| 延迟转化 | 点击后几小时甚至几天才付费。训练时如果把"还没转化"当负样本,pCVR 会偏低。做法:等待窗口、先作为负样本后修正、对转化延迟建模(参考 Modeling Delayed Feedback in Display Advertising,KDD 2014) |
| 训练与服务一致性 | 在线预估时把用到的特征值随请求日志落盘,训练时直接用这份快照拼样本,避免离线重新计算特征导致的偏差 |
| 模型更新频率 | 流式训练(分钟到小时级增量更新参数),新广告、新素材很快就能学到 |
样本拼接
flowchart LR
REQ[请求日志<br/>trace_id + 特征快照] --> JOIN[按 trace_id 拼接<br/>Flink 窗口 Join]
IMP[曝光日志] --> JOIN
CLK[点击日志] --> JOIN
CV[转化回传<br/>按 click_id 关联] --> JOIN
JOIN --> SAMPLE[训练样本<br/>特征 + 是否点击 + 是否转化]
SAMPLE --> TRAIN[流式训练]
TRAIN --> MODEL[模型发布<br/>参数同步到预估服务]
所有日志都靠在线请求时生成的 trace_id(及派生的 click_id)串起来,这是整个数据闭环的主键。
3.8 竞价、出价与混排
eCPM 排序
CPC 广告: eCPM = bid_cpc × pCTR × 1000
oCPA 广告: eCPM = bid_cpa × pCTR × pCVR × 1000
CPM 广告: eCPM = bid_cpm
不同计费方式的广告统一换算成 eCPM 后放在一起排序。实际排序分还会加上质量和体验项:
rank_score = eCPM + 质量 / 体验调节项(负反馈、素材质量、落地页质量)
计费价:GSP
广义第二价格(GSP)拍卖中,第 1 名的计费价由第 2 名的排序分决定:
charge_cpc(1) = eCPM(2) / (pCTR(1) × 1000) + 0.01
赢家只付"刚好能赢第二名"的价格,这让广告主倾向于按真实价值出价。另外有保留价(reserve price): 只有一个候选或第二名很低时,计费价不低于保留价,保护媒体收入。
oCPX:平台替广告主出价
oCPA 广告主出的是"每个转化 100 元"。平台每次曝光的实际出价是:
bid_per_imp = bid_cpa × pCTR × pCVR × 调节系数
| 阶段 | 做法 |
|---|---|
| 第一阶段(学习期) | 转化数据不足,pCVR 不准。按 CPC / CPM 方式投放积累转化;或用同行业、同账户的先验 |
| 第二阶段(智能出价) | 转化数据足够后开启 oCPA,按上式出价 |
| 成本控制 | 实际 CPA 高于目标时调低调节系数,低于目标时调高,常用 PID 控制器按小时迭代 |
| 成本保障 | 各平台有成本保障(赔付)规则:满足转化数门槛后实际成本超出目标一定比例,超出部分返还。条件和比例以平台当期规则为准 |
在此之上的自动出价产品(最大转化、ROI 出价、预算内拿量)本质都是带约束的在线优化: 在预算约束下最大化转化,或在 ROI 约束下最大化 GMV,用对偶变量或控制器调节每次曝光的出价。
混排
信息流中广告和自然内容一起排:
| 约束 | 说明 |
|---|---|
| 广告负载率 | 每 N 条内容最多 1 条广告,首屏不出广告等 |
| 广告间隔 | 两条广告之间至少间隔若干条内容 |
| 价值统一 | 把自然内容的用户价值(预估时长、互动)和广告的 eCPM 换算到同一尺度,决定某个坑位放广告还是内容 |
| 负反馈 | 用户对某类广告点过"不感兴趣",后续降低出现概率 |
3.9 实时状态:预算、频控与匀速投放
预算
广告主设置日预算 1 万元。在线引擎需要知道每条广告"今天已经花了多少",这个数来自计费流:
flowchart LR
BILL[计费事件] --> AGG[Flink 按账户 / 计划 / 广告<br/>秒级聚合花费]
AGG --> KV[(预算状态 KV<br/>今日已花费)]
KV -- 周期推送或拉取 --> ONLINE[在线节点本地缓存]
ONLINE --> DECIDE{已花费 ≥ 预算?}
DECIDE -- 是 --> STOP[过滤掉]
DECIDE -- 接近 --> SLOW[降低参竞概率]
| 问题 | 做法 |
|---|---|
| 匀速 | 按历史流量曲线把日预算拆到每个时间片;实际花费超前就降低参竞概率(throttling)或压低出价,落后就放开 |
| 超投 | 曝光 / 点击到扣费有延迟,这段时间内广告仍在参竞,最终花费超过预算。做法:预算接近上限时提前降速;缩短计费链路延迟;超出部分平台不收费 |
| 冷启动 | 新广告没有 pCTR / pCVR 数据,给探索流量,或使用同类广告的先验 |
匀速投放的参考:Budget Pacing for Targeted Online Advertisements at LinkedIn(KDD 2014)、 Smart Pacing for Effective Online Ad Campaign Optimization(KDD 2015)。
超投的成因可以量化:假设一条广告每秒消耗 10 元,从计费到在线节点感知"预算耗尽"的延迟是 30 秒, 那么最多超投约 300 元。降低超投的两个方向是缩短感知延迟和提前减速。
频控
key = 用户ID : 广告主ID(或广告ID) : 日期 value = 曝光次数 TTL = 1 天
频控计数允许少量误差:用 Redis 计数、或在线节点把最近曝光记录在用户特征里随请求带回。 频控维度可以是广告、广告主、行业(同一用户一天内不连续看到同一行业的广告)。
3.10 监测、反作弊与计费
flowchart LR
CLIENT[客户端 / SDK] -- 曝光 / 点击上报 --> TRACK[监测服务]
TRACK --> LOG[原始日志 Kafka]
LOG --> DEDUP[去重<br/>按 trace_id / click_id]
DEDUP --> RT_ANTI[实时反作弊<br/>规则:频率、IP、设备异常]
RT_ANTI --> CHARGE[实时计费<br/>扣余额、累计花费]
CHARGE --> ACCT[(账户资金)]
LOG --> OFFLINE[离线反作弊<br/>模型识别刷量]
OFFLINE --> REFUND[退款 / 冲正]
CHARGE --> RECON[T+1 对账<br/>实时账 vs 离线账]
REFUND --> ACCT
| 环节 | 要点 |
|---|---|
| 监测链接 | 返回广告时带签名的监测 URL(含 trace_id、计费价);客户端上报时服务端校验签名,防止伪造 |
| 去重 | 同一次展示的重复曝光、同一个点击的重复上报只计一次,按 trace_id / click_id 幂等 |
| 实时反作弊 | 单设备短时间大量点击、IP 段异常、模拟器、点击坐标异常,命中直接不计费 |
| 离线反作弊 | 事后用模型识别作弊流量(联盟流量是重灾区),已扣的钱退回 |
| 计费一致性 | 计费事件用 Kafka + Flink 精确一次语义处理;扣款操作以事件 ID 幂等 |
| 对账 | 实时计费是近似值,T+1 离线重算为准,差异生成冲正流水 |
3.11 实验平台
广告系统的每个改动(模型、出价策略、混排参数)都要 AB 实验验证,而且同时在跑的实验有几百个。 常用分层实验架构(参考 Google Overlapping Experiment Infrastructure,KDD 2010):
- 流量按层划分,每层内实验互斥,不同层之间正交(用不同的哈希种子分桶)
- 召回层、预估层、竞价层、混排层各自一层
- 广告实验有一个特殊问题:两组广告共享预算,一组出价高会抢走另一组的预算,结果有偏。 需要按广告主或按预算做分组,或在实验组之间分割预算
3.12 报表与数据仓库
广告主需要分钟级的实时报表(花费、曝光、点击、转化、CPA)。
| 层 | 做法 |
|---|---|
| 实时 | Flink 聚合后写入 OLAP(ClickHouse、Druid、Doris),按账户、计划、广告、创意、小时多维度查询 |
| 离线 | 每日在数据仓库重算,覆盖实时结果(离线结果包含反作弊退款和延迟转化) |
| 一致性 | 实时报表和最终账单可能不一致,报表标注"数据有延迟,以账单为准" |
4. 品牌合约广告(GD)
4.1 目的
品牌广告主不按点击付费,而是按曝光(CPM)或时长(CPT)购买,并要求保量: "春节 7 天,微信朋友圈,一线城市 25~40 岁女性,保证 5000 万次曝光"。媒体提前签合同、收钱, 没投够要补量或赔付。
4.2 架构
flowchart TB
subgraph 售卖["售卖期(投放前几天到几个月)"]
FORECAST[库存预估<br/>未来某定向组合有多少曝光]
BOOK[询量 / 下单<br/>检查可售库存,锁量]
FORECAST --> BOOK
end
subgraph 分配["分配(每天 / 每小时)"]
PLAN[离线分配<br/>求解每个合约的分配参数]
end
subgraph 在线["在线执行"]
REQ[广告请求] --> MATCH[找出满足定向的合约]
MATCH --> SELECT[按分配参数<br/>概率选择合约]
SELECT --> FREQ[频控 / 素材轮播]
end
subgraph 监控["投放监控"]
TRACK[实际曝光统计]
TRACK -- 进度落后 / 超前 --> PLAN
end
BOOK --> PLAN --> SELECT
FREQ --> TRACK
4.3 库存预估
问题:给定一个定向组合(地域 × 年龄 × 性别 × 兴趣 × 版位 × 日期),预测未来有多少次曝光。
- 定向组合是指数级的,不能对每个组合单独建模
- 常见做法:采样日志回放。从历史请求日志中采样(比如 1%),每条样本带完整用户属性; 询量时把定向条件在样本上过一遍,数出满足条件的样本数,按采样率和时间序列模型(周期、节假日、增长趋势)放大到未来日期
- 多个合约的定向重叠时,同一次曝光只能卖一次。可售库存 = 满足定向的总库存 − 已被其他合约占用的部分, "占用"要在分配模型里计算,不能简单相减
4.4 分配
分配问题可以建模成二分图:左边是供给节点(一类属性相同的流量,例如"上海 25-34 岁女性 × 朋友圈"), 右边是需求节点(合约)。边表示流量满足合约定向。目标:
- 每个合约的需求量都被满足(硬约束,满足不了要算缺量惩罚)
- 每个合约拿到的流量在其可用流量中尽量均匀(不让一个合约吃光某类优质流量,损害其他合约和竞价广告)
| 算法 | 思路 |
|---|---|
| HWM(High Water Mark) | 按可用供给从少到多给合约排序;依次为每个合约计算一个比例 α_j,使它在剩余供给中按 α_j 取量恰好满足需求。在线时按同样顺序,每个合约以概率 α_j 拿走这次曝光。简单、可在线执行,但不是最优 |
| SHALE(Yahoo) | 基于对偶的迭代算法,输出每个合约的对偶变量;在线用对偶变量计算这次曝光分给每个合约的概率。比 HWM 更接近最优,计算量更大 |
参考:Ad Serving Using a Compact Allocation Plan(EC 2012)。两种算法的共同点是:离线只输出每个合约一个(或少量)参数, 在线请求到来时根据参数现场算出分配概率。这让分配计划体积很小,可以快速下发到所有在线节点。
4.5 在线执行与和竞价广告的混合
| 问题 | 做法 |
|---|---|
| 执行偏差 | 流量预估有误差,实际曝光偏离计划。按小时监控进度,落后的合约提高 α,超前的降低 |
| 频控 | 品牌广告一般要求同一用户每天看到 N 次以内,频控在选择合约时过滤 |
| 与竞价混合 | 同一流量上 GD 合约和竞价广告共存。简单做法是 GD 优先;更好的做法是给 GD 一个"虚拟出价"(按保量紧迫程度动态变化)和竞价广告统一比较,只在保量需要时才占用高价值流量 |
| 缺量 | 投放结束仍未达成,补量(延期投放)或按合同赔付 |
5. 程序化交易(RTB:DSP / SSP / ADX)
5.1 目的与角色
媒体没有自己的广告主团队(或有卖不完的剩余流量),广告主想跨多个媒体统一投放。 程序化交易让每一次曝光单独拍卖。
| 角色 | 代表谁 | 做什么 |
|---|---|---|
| SSP(Supply-Side Platform) | 媒体 | 管理媒体广告位,接入多个 ADX / DSP,设底价,最大化媒体收入 |
| ADX(Ad Exchange) | 中立市场 | 把一次曝光广播给多个 DSP,收集出价,决出赢家,负责结算 |
| DSP(Demand-Side Platform) | 广告主 | 对每次曝光判断"值不值得买、出多少钱" |
| DMP(Data Management Platform) | 数据 | 提供人群标签和人群包 |
交易类型:
| 类型 | 价格 | 是否保量 | 说明 |
|---|---|---|---|
| RTB(公开竞价) | 竞价 | 否 | 所有 DSP 公开竞争 |
| PMP(私有竞价) | 竞价,有底价 | 否 | 只邀请部分买方参与 |
| PD(优先交易) | 固定价 | 否 | 买方有优先购买权,可以挑选 |
| PDB(程序化保量) | 固定价 | 是 | 程序化方式执行的合约广告,和第 4 节 GD 对应 |
5.2 一次竞价的时序
sequenceDiagram
participant App as 媒体 App
participant SSP
participant ADX
participant D1 as DSP-1
participant D2 as DSP-2
App->>SSP: 广告请求
SSP->>ADX: Bid Request(设备、位置、广告位尺寸、底价)
par 并行广播,超时 tmax
ADX->>D1: OpenRTB BidRequest
ADX->>D2: OpenRTB BidRequest
end
D1-->>ADX: BidResponse(出价 + 素材)
D2-->>ADX: 不出价(HTTP 204)
ADX->>ADX: 竞价决出赢家
ADX->>D1: win notice(nurl,带清算价)
ADX-->>SSP: 赢家素材
SSP-->>App: 渲染广告
App->>D1: 曝光监测 / billing notice(burl)
5.3 OpenRTB 协议要点
IAB Tech Lab 的 OpenRTB 是 ADX 和 DSP 之间的事实标准,当前主流版本 2.5 / 2.6。国内 ADX 多数基于它扩展或自定私有协议。
BidRequest 主要对象:
| 字段 | 含义 |
|---|---|
id |
请求 ID |
imp[] |
一个或多个广告位,含 banner / video / native 规格、bidfloor 底价、pmp(私有交易的 deal 列表) |
site / app |
媒体信息(域名 / 包名、类目) |
device |
设备信息(UA、IP、操作系统、设备 ID) |
user |
用户信息(买方用户 ID、DMP 数据) |
at |
拍卖类型:1 = 一价,2 = 二价(Second Price Plus) |
tmax |
DSP 最大响应时间(毫秒) |
bcat / badv |
媒体屏蔽的广告类目 / 广告主域名 |
BidResponse 主要对象:seatbid[].bid[],每个 bid 含 impid(对应哪个广告位)、price(出价)、
adm(素材标记)、nurl(胜出通知 URL)、burl(计费通知 URL)、lurl(竞价失败通知 URL)、dealid。
ADX 在 nurl / burl 中用宏 ${AUCTION_PRICE} 替换出清算价,许多 ADX 对这个价格加密,DSP 用约定密钥解密后记账。
5.4 DSP 架构
flowchart TB
subgraph 在线["Bidder 集群(每秒大量请求,tmax 内返回)"]
IN[接入<br/>协议解析、QPS 过滤]
UID[用户识别<br/>设备 ID / Cookie Mapping]
PROFILE[用户画像 KV]
MATCH[活动匹配<br/>定向倒排]
PRED[预估<br/>pCTR / pCVR / 赢率]
BID[出价<br/>价值估计 + bid shading]
BUDGET[预算 / 频控检查]
IN --> UID --> PROFILE --> MATCH --> PRED --> BID --> BUDGET
end
subgraph 离线
CAMP[活动管理<br/>广告主配置]
AUDIT[素材送审<br/>ADX 预审]
LOGS[竞价 / 胜出 / 曝光 / 点击日志]
TRAIN[模型训练]
REPORT[报表、结算对账]
end
CAMP --> MATCH
BUDGET --> LOGS --> TRAIN --> PRED
LOGS --> REPORT
| 模块 | 要点 |
|---|---|
| QPS 过滤 | ADX 发来的请求量远大于 DSP 能买的量,大部分请求 DSP 根本不会出价。在接入层先按媒体、广告位、地域等粗粒度条件过滤,或和 ADX 约定只接收特定流量,节省计算 |
| 用户识别 | Web 场景下 DSP 和 ADX 的 cookie 不同,需要 Cookie Mapping(通过像素请求交换双方 ID);App 场景用设备 ID(IDFA、OAID、CAID 等)。iOS 14.5 之后 IDFA 需要用户通过 ATT 授权,可识别用户比例大幅下降 |
| 出价 | 价值 = pCTR × pCVR × 每个转化对广告主的价值;再根据拍卖类型决定出多少 |
| 预算 | 全局预算在多个 bidder 节点之间分配,节点本地扣减、定期同步,避免每次出价都访问中心存储 |
| 素材审核 | 很多 ADX 要求素材提前送审,审核通过的素材 ID 才能出现在响应里 |
| 结算 | DSP 按 ADX 返回的清算价记账,与 ADX 对账;再按和广告主约定的方式(加价、服务费)向广告主收费 |
5.5 一价拍卖与 bid shading
早期 ADX 多为二价拍卖:出价只决定能否赢,赢了付第二高价,DSP 按真实价值出价即可。 Google Ad Manager 在 2019 年切换到统一一价拍卖,行业随之普遍转向一价:赢了付自己的出价。
一价下按真实价值出价会多付钱。DSP 需要估计赢率曲线 P(win | bid),选择使期望收益最大的出价:
bid* = argmax_b (value − b) × P(win | b)
P(win | b) 从历史竞价结果中学习:赢了知道自己赢了,输了能从 lurl 或 ADX 反馈中得到最低胜出价(部分 ADX 提供)。
这就是 bid shading。
5.6 SSP 侧与 Header Bidding
| 问题 | 做法 |
|---|---|
| 底价优化 | 底价太高卖不出去,太低少赚钱。按广告位、时段、用户价值动态设置底价 |
| 多 ADX 接入 | 传统方式是瀑布流(waterfall):按优先级依次询价,前一家不要才问下一家,延迟高且不是价高者得 |
| Header Bidding | 在页面或 App 端同时向多个需求方询价,再把最高价带给主广告服务器比较(Web 端常用开源的 Prebid.js),让所有需求方同时竞争 |
6. 搜索广告与电商搜索广告
6.1 目的
用户在搜索框输入"信用卡申请""跑步鞋"时,意图明确,这类流量转化率高。广告主(商家)按关键词购买, 广告和自然搜索结果一起展示。
6.2 架构
flowchart LR
Q[用户查询] --> QP[查询理解<br/>分词、纠错、意图、类目]
QP --> REWRITE[查询改写<br/>同义词、扩展词]
REWRITE --> KWRET[关键词召回<br/>查询 → 广告主购买的关键词]
QP --> SEMRET[语义召回<br/>向量检索]
KWRET & SEMRET --> REL[相关性过滤]
REL --> RANK[预估 pCTR / pCVR]
RANK --> AUC[竞价<br/>出价 × 质量]
AUC --> MIX[与自然结果混排]
| 模块 | 要点 |
|---|---|
| 匹配方式 | 广告主选择关键词的匹配方式:精确匹配(查询和关键词一致)、短语匹配(查询包含关键词)、广泛匹配(语义相关即可)。广泛匹配拿量多但相关性风险高 |
| 查询改写 | 用户查询千变万化,广告主买的词有限。查询改写把"跑鞋 男 减震"改写到广告主买的"男士跑步鞋"上,是召回量的主要来源 |
| 相关性 | 搜索广告对相关性要求比信息流高:搜"信用卡"出游戏广告会严重伤害体验。相关性模型有独立的阈值,不够相关的出价再高也不出 |
| 质量分 | 排序分 = 出价 × 质量分,质量分综合 pCTR、相关性、落地页体验。质量差的广告要出更高价才能排上去 |
| 电商特点 | 商品即创意,商品标题、主图、价格、销量都是特征;转化(下单)在平台内完成,平台能直接拿到成交数据,不依赖广告主回传;近年趋势是商家只设置 ROI 目标,由平台自动选词、出价(全站推广类产品) |
7. 广告主侧买量平台
7.1 目的
一家游戏公司或金融公司每月在腾讯广告、巨量引擎、快手磁力、百度等平台花上千万买用户。投手的痛点:
- 每个媒体的后台都要单独登录、单独建计划,素材要重复上传
- 成本(CPA)、ROI 要跨媒体统一看,而媒体后台只看得到自己那一段
- 调价、关停、复制计划靠人工盯盘,反应慢
- 媒体报表里的"转化"和公司自己数据库里的"付费用户"对不上
广告主侧投放平台不拥有流量,也不做竞价,它是一个媒体 API 的编排层 + 归因和回传系统 + ROI 分析 + 自动化规则引擎, 进阶的还会通过 RTA 实时参与媒体的投放决策。
7.2 架构
flowchart TB
subgraph 前台
WEB[投手工作台<br/>批量建计划 / 素材库 / 报表 / 规则配置]
end
subgraph 投放管理
TPL[计划模板<br/>定向包、出价策略、创意组合]
TASK[任务系统<br/>批量任务拆分、排队、重试]
RULE[自动化规则引擎<br/>成本超标关停、跑量加预算]
MAT[素材中心<br/>上传、去重、标签、效果统计]
SYNC[状态同步<br/>定期拉取媒体侧物料]
end
subgraph 媒体适配层
AUTH[授权管理<br/>OAuth token 刷新]
A1[腾讯广告 Marketing API 适配器]
A2[巨量引擎开放平台适配器]
A3[快手 / 百度 / 其他适配器]
LIMIT[限流<br/>按媒体、按账户令牌桶]
end
subgraph 数据
PULL[报表拉取<br/>花费、曝光、点击,按小时]
TRACKSVR[监测服务<br/>接收媒体点击监测]
ATTR[归因服务]
BIZ[业务事件<br/>激活、注册、授信、付费]
CB[回传服务]
DW[(数据仓库<br/>ROI / LTV)]
end
subgraph 实时
RTA[RTA 服务<br/>实时决定是否参竞]
end
WEB --> TPL --> TASK
WEB --> MAT
TASK & RULE & MAT & SYNC --> LIMIT --> A1 & A2 & A3
AUTH --> A1 & A2 & A3
A1 & A2 & A3 --> PULL --> DW
TRACKSVR --> ATTR
BIZ --> ATTR --> DW
ATTR --> CB --> A1 & A2 & A3
DW --> RULE
DW --> WEB
DW --> RTA
7.3 统一物料模型与媒体适配
各媒体的物料层级、字段、枚举值都不同。内部定义一套统一模型,每个媒体一个适配器负责双向转换:
| 内部概念 | 腾讯广告 | 巨量引擎 | 说明 |
|---|---|---|---|
| 账户 | 广告账户 | 广告主账户 | 通过 OAuth 授权接入 |
| 计划 | 推广计划 / 广告 | 项目 / 广告 | 层级名称和字段随各平台版本调整 |
| 定向 | 定向包 | 定向包 | 地域、兴趣编码体系不同,需要映射表 |
| 创意 | 创意 | 创意 | 素材规格(尺寸、时长)各自要求 |
实现要点:
- 本地保存
内部 ID ↔ 媒体 ID映射;媒体特有字段放在扩展 JSON 里,不强行统一 - 地域、兴趣、行业这类枚举定期从媒体 API 拉取,维护映射表
- 媒体 API 版本升级时只改对应适配器
7.4 任务系统:批量操作的可靠执行
"用 3 个定向包 × 5 套素材 × 4 个出价,在 10 个账户上各建一批计划",一次就是几百次 API 调用。
stateDiagram-v2
[*] --> 待执行
待执行 --> 执行中: 获取令牌
执行中 --> 成功: API 返回成功
执行中 --> 待重试: 限流 / 超时 / 5xx
待重试 --> 执行中: 指数退避后
执行中 --> 失败: 参数错误 / 超过重试次数
待重试 --> 失败: 超过重试次数
成功 --> [*]
失败 --> [*]
| 问题 | 做法 |
|---|---|
| 拆分 | 一个批量任务拆成子任务,每个子任务对应一次 API 调用,独立记录状态 |
| 限流 | 各媒体对 API 有 QPS 和日调用量配额(按应用、按账户)。本地按媒体 × 账户维护令牌桶,超额的子任务排队 |
| 重试 | 限流错误和网络错误按指数退避重试;参数错误直接失败并把媒体返回的错误信息展示给投手 |
| 幂等 | 创建类操作超时后不知道媒体侧是否已创建。重试前先按名称或外部标识查询媒体侧是否已存在,避免重复创建 |
| 部分失败 | 批量任务允许部分成功,结果页列出失败项和原因,支持一键重试失败项 |
7.5 授权与状态同步
- 授权:媒体开放平台使用 OAuth 2.0,广告主授权后拿到 access_token 和 refresh_token。 access_token 有效期较短,后台定时刷新;刷新失败要告警,否则所有操作都会失败。token 的有效期以各平台文档为准
- 状态同步:投手也会直接在媒体后台改计划,媒体也会因审核拒绝、余额不足自动改变状态。本地状态以媒体为准, 定期全量拉取 + 对变化频繁的字段(状态、预算、出价)增量拉取
7.6 报表拉取
- 按小时拉取各媒体的花费、曝光、点击报表,写入数据仓库
- 媒体报表会被修正(反作弊扣除、延迟数据补入),需要回溯重拉最近几天的数据覆盖旧值
- 不同媒体的时区、货币单位、字段口径不同,入库时统一
7.7 归因
一个用户安装了 App,他是从哪个媒体、哪个广告来的?
sequenceDiagram
participant U as 用户
participant M as 媒体
participant T as 广告主监测服务
participant App as 广告主 App / 服务端
participant ATTR as 归因服务
U->>M: 点击广告
M->>T: 点击监测(click_id、广告 ID、设备 ID、IP、UA、时间)
T->>ATTR: 记录点击
U->>App: 下载并激活
App->>ATTR: 激活事件(设备 ID、IP、UA、时间)
ATTR->>ATTR: 在归因窗口内查找匹配点击<br/>设备 ID 精确匹配,否则 IP + UA 模糊匹配
ATTR->>ATTR: 多个媒体都有点击 → 按规则(通常末次点击)选一个
ATTR-->>App: 归因结果(渠道、计划、click_id)
| 问题 | 说明 |
|---|---|
| 匹配方式 | 设备 ID(IMEI 已基本不可用;Android 用 OAID,iOS 用授权后的 IDFA 或 CAID)精确匹配;拿不到设备 ID 时用 IP + UA 模糊匹配,准确率低 |
| 归因窗口 | 点击后多久内的激活算这次点击的功劳,常见 7 天,可配置 |
| 多触点 | 用户可能先后点过多个媒体的广告。广告主自己的归因以末次点击为主,媒体各自报表可能都认领了这个激活,这是媒体数据加总大于实际的原因 |
| 自然量 | 匹配不到任何点击的激活算自然量 |
| 第三方 MMP | AppsFlyer、Adjust、热云等提供现成 SDK 和归因服务,也是媒体认可的中立第三方 |
7.8 转化回传
归因确定这个用户来自腾讯广告的某次点击后,把转化事件回传给腾讯广告,让媒体的 oCPX 模型学习:
| 要点 | 说明 |
|---|---|
| 事件映射 | 业务事件(注册、授信、首次付费、次日留存)映射到媒体定义的转化类型 |
| 时效 | 回传越快,媒体模型学得越快;深度事件(付费)本身有延迟,媒体侧有回传时间窗口 |
| 口径 | 回传哪些事件、回传给哪个优化目标,决定了媒体会去找什么样的用户。金融行业常用"授信"而不是"注册"作为优化目标,因为注册用户质量差异大 |
| 可靠性 | 回传失败要重试,按事件 ID 幂等,避免重复回传导致媒体统计的转化偏多 |
7.9 RTA:广告主实时参与投放决策
Marketing API 只能事先配置"投给谁";RTA(Real-Time API)让广告主在每次广告请求时参与决策:
sequenceDiagram
participant U as 用户
participant M as 媒体广告引擎
participant R as 广告主 RTA 服务
U->>M: 广告请求
M->>R: RTA 请求(加密设备 ID 等,超时几十毫秒量级)
R->>R: 查用户:已是存量用户?风控黑名单?预测 LTV?
R-->>M: 参竞 / 不参竞(可附带出价系数、指定广告)
M->>M: 只让广告主同意的广告参与竞价
M-->>U: 广告
| 用途 | 说明 |
|---|---|
| 排除存量用户 | 已经是客户的用户不再花钱拉新 |
| 风控 | 金融广告主排除高风险、多头借贷用户 |
| 分层出价 | 按预测 LTV 返回出价系数,高价值用户出高价 |
| 数据不出域 | 广告主的用户数据不必上传给媒体,只返回"是否参竞",隐私和数据安全压力更小 |
RTA 服务的要求和 DSP 类似:媒体侧有严格超时(超时视为默认策略),QPS 跟随媒体的广告请求量, 因此要用内存 KV 存用户分层结果,模型打分离线完成,在线只做查表。
7.10 自动化规则与 ROI
规则示例:
条件:近 3 小时 CPA > 目标 CPA × 1.5 AND 花费 > 500 元
动作:暂停广告
频率:每 30 分钟评估一次
保护:单次调价幅度不超过 20%;白名单计划不动;同一计划 1 小时内最多执行一次
- 规则引擎定时从数据仓库取指标,评估条件,命中后生成任务交给任务系统执行,所有执行记录留痕可回滚
- ROI = 收入 / 花费。花费来自媒体报表,收入来自业务库,按计划 / 素材 / 渠道关联。 付费会持续发生,首日 ROI 只是开始,决策需要预测 7 日、30 日 LTV
- 素材中心按素材 MD5 去重,统计每个素材在所有媒体上的效果,找出"爆款素材"复用到其他计划
7.11 和媒体侧广告系统的区别
| 媒体侧广告系统 | 广告主侧买量平台 | |
|---|---|---|
| 拥有流量 | 是 | 否 |
| 在线请求 | 每次曝光一次请求,QPS 极高,毫秒级 | 主要是批量任务和报表,分钟级;只有 RTA 是毫秒级在线服务 |
| 核心能力 | 检索、预估、竞价、计费 | API 编排、归因、回传、ROI 分析、自动化规则 |
| 优化对象 | 平台总收入 + 广告主效果 | 广告主自己的 ROI |
| 数据 | 看到全量用户行为,看不到广告主后链路 | 看到自己的业务后链路,看不到媒体用户数据 |
8. 横向对比
| 维度 | 媒体竞价广告 | 品牌 GD | RTB / DSP | 搜索广告 | 广告主买量平台 |
|---|---|---|---|---|---|
| 谁出钱 | 广告主 | 品牌广告主 | 广告主 | 广告主 / 商家 | 广告主(花在媒体) |
| 谁决策 | 模型 + 竞价 | 离线分配 + 在线执行 | DSP 出价 + ADX 竞价 | 相关性 + 竞价 | 投手 + 规则 + RTA |
| 在线延迟要求 | 几十毫秒 | 几十毫秒 | DSP 需在 tmax 内(约 100ms) | 几十毫秒 | RTA 几十毫秒;其余无在线链路 |
| QPS 特征 | 极高 | 高 | 极高 | 高 | 批量任务低;RTA 高 |
| 一致性要求 | 计费要准,可最终一致 | 保量 | 计费要准 | 计费要准 | 与媒体状态最终一致 |
| 核心模块 | 检索、预估、竞价、计费、pacing | 库存预估、分配 | 用户识别、出价、bid shading | 查询理解、改写、相关性 | API 适配、任务系统、归因、回传、RTA |
| 数据闭环周期 | 实时到小时级 | 小时级 | 实时到小时级 | 实时到小时级 | 小时到天级 |
9. 广告投放系统共有的工程问题
9.1 物料到在线的发布
| 方案 | 做法 |
|---|---|
| 全量 + 增量 | 定时生成全量索引快照;binlog 或消息队列推送增量,在线服务在内存中实时更新倒排和正排 |
| 版本化快照 | 全量索引带版本号,在线服务加载新版本后原子切换;出问题回滚到上一版本 |
| 状态与物料分离 | 预算耗尽、频控这类高频变化的状态不进索引,放在单独的实时状态服务里,过滤阶段查询 |
9.2 计数的精度分级
| 计数 | 精度要求 | 做法 |
|---|---|---|
| 频控 | 允许少量误差 | Redis 计数、本地计数定期汇总 |
| 预算状态(在线侧) | 近似即可,用于提前降速 | 流式聚合后推送到在线节点 |
| 计费、余额 | 必须准确可对账 | 幂等扣款、精确一次流处理、T+1 离线重算 |
9.3 追踪 ID 与数据闭环
在投放响应里带上追踪 ID(trace_id、广告 ID、click_id),曝光监测、点击监测、转化回传都携带它, 才能把"展示了什么"和"用户后来做了什么"连起来。这份数据同时用于计费、报表、样本拼接和归因。
9.4 高可用
| 手段 | 说明 |
|---|---|
| 超时降级 | 召回分片超时丢弃该分片;精排超时退化到粗排分;特征超时用默认值 |
| 容量 | 在线服务无状态、水平扩展;索引多副本 |
| 计费链路 | 日志先落 Kafka,下游故障恢复后重放,不丢钱 |
| 发布安全 | 模型、索引、策略都支持灰度和快速回滚 |
9.5 隐私与合规
- 《个人信息保护法》等法规和操作系统政策(iOS ATT)限制了设备 ID 的获取和跨方共享, 归因、定向、DSP 用户识别都受影响
- 应对方向:RTA(数据不出域)、加密人群包匹配、隐私计算 / 联邦学习联合建模、媒体侧的聚合归因
- 行业资质:金融、医疗、教育广告有额外的资质审核和素材规范
10. 和站内运营位投放的区别
站内运营位(App 首页 banner、弹窗、理财通首页的基金推荐卡片)也叫"投放",但它投的是自有流量,不卖钱:
| 媒体竞价广告 | 站内运营位投放 | |
|---|---|---|
| 候选规模 | 百万级广告 | 一个位置同时生效的计划通常是个位数到几百 |
| 召回 | 布尔表达式倒排 + 模型召回 | 位置 → 计划的直接映射 |
| 排序 | eCPM = 出价 × 预估 | 运营优先级 |
| 钱 | 每次曝光 / 点击都在扣钱 | 不涉及计费 |
| 目标 | 平台收入 + 广告主 ROI,模型自动优化 | 业务 KPI,运营人工迭代 |
详见 运营位投放系统.md。
参考
- IAB Tech Lab, OpenRTB API Specification 2.5 / 2.6
- Whang, S. E. et al. Indexing Boolean Expressions. VLDB 2009
- Bharadwaj, V. et al. Ad Serving Using a Compact Allocation Plan(HWM / SHALE). EC 2012
- Agarwal, D. et al. Budget Pacing for Targeted Online Advertisements at LinkedIn. KDD 2014
- Xu, J. et al. Smart Pacing for Effective Online Ad Campaign Optimization. KDD 2015
- Chapelle, O. Modeling Delayed Feedback in Display Advertising. KDD 2014
- Ma, X. et al. Entire Space Multi-Task Model(ESMM). SIGIR 2018
- Zhou, G. et al. Deep Interest Network for Click-Through Rate Prediction(DIN). KDD 2018
- Tang, D. et al. Overlapping Experiment Infrastructure: More, Better, Faster Experimentation. KDD 2010
暂无评论,欢迎留下第一条评论。