广告系统设计
一份从系统架构到工程实践的完整技术指南,涵盖广告投放全链路、核心算法机制、常见工程问题及解决方案。
什么是广告系统
广告系统是连接 广告主(需求方)、媒体(供给方)和 用户 的三方平台。其核心目标是在正确的时间、正确的场景,将正确的广告展示给正确的用户,同时实现广告主 ROI 最大化、媒体收益最大化和用户体验最优的三角平衡。
核心公式:eCPM = CPC × CTR × 1000 = CPA × CVR × CTR × 1000
关键业务指标
| 指标 | 全称 | 含义 |
|---|---|---|
| CPM | Cost Per Mille | 千次展示成本 |
| CPC | Cost Per Click | 单次点击成本 |
| CPA | Cost Per Action | 单次转化成本 |
| CTR | Click-Through Rate | 点击率 = 点击数 / 展示数 |
| CVR | Conversion Rate | 转化率 = 转化数 / 点击数 |
| eCPM | Effective CPM | 有效千次展示收益 |
| Fill Rate | 填充率 | 有广告返回的请求 / 总请求 |
| Win Rate | 竞价胜率 | 竞价成功次数 / 参与竞价次数 |
业务参与者
| 角色 | 核心诉求 | 关注指标 |
|---|---|---|
| 广告主 | 精准触达目标用户,控制成本,最大化 ROI | CPA、ROAS(广告支出回报率)、转化成本 |
| 媒体 | 充分售卖流量,提升收益,不影响用户体验 | eCPM、Fill Rate、ARPU |
| 用户 | 看到相关广告,不被过度打扰 | 广告相关性、展示频率 |
| 平台 | 撮合供需,技术支撑,数据驱动优化 | GMV、平台抽佣率、系统稳定性 |
核心架构
整体架构分层
各层职责详解
| 层级 | 核心职责 | 关键技术 | SLA 要求 |
|---|---|---|---|
| 接入层 | 协议解析、参数校验、流量路由、限流熔断 | OpenResty / Envoy / Kong | 可用性 99.99%、延迟 <5ms |
| 广告引擎 | 多路召回、粗精排、竞价排序、创意优选 | C++ / Go,自研 Ranker | P99 <60ms、QPS >100K/实例 |
| 数据 & 策略层 | 用户画像、广告索引、频控/预算状态 | Redis / ES / Milvus | P99 <5ms、数据准实时(<1s) |
| 模型服务层 | CTR/CVR 预估、Embedding 向量计算 | TF Serving / Triton | P99 <20ms |
| 数据管道 | 实时计费、反作弊、报表、特征回写 | Kafka+Flink / Spark | 端到端延迟 <30s |
广告投放完整链路
一次广告展示从用户打开 App 到看到广告,背后经历了 10 个关键步骤,整个过程需要在 100ms 以内完成。
链路流程
用户请求触发 t=0ms
用户滑动信息流 / 打开页面时,客户端 SDK 向 Ad Server 发送广告请求,携带 device_id、广告位 ID、上下文信息(内容标签、地理位置等)。
流量接入与校验 t=2ms
网关层完成协议解析、参数校验、流量染色标记。对异常流量(黑名单 IP、高频请求)进行识别和拒绝。
多路召回 t=5ms
从多个维度并行检索候选广告:用户定向匹配(倒排索引)、向量相似召回(Embedding)、热门/兜底广告。从百万级广告库中召回数千个候选。
预算 & 频控过滤 t=8ms
过滤预算耗尽、频次达到上限、不在投放时段的广告。读取 Redis 中的实时预算状态和用户维度频控计数器。
粗排(Pre-Ranking) t=15ms
用轻量级模型对数千个候选广告快速打分,截断保留 Top 200~500。关注计算效率,允许一定的精度损失。
精排(Ranking) t=35ms
使用深度模型(DeepFM / DCN / DIN)对每个广告进行 CTR 和 CVR 预估。结合广告主出价计算 eCPM = bid × pCTR × pCVR × 1000。按 eCPM 降序排列。
重排(Re-Ranking) t=45ms
考虑用户体验约束:同类目打散、信息流中广告位置间隔控制。引入多样性、新鲜度因子,避免用户疲劳。
创意优选 t=50ms
对胜出广告选择最优创意(图片、视频、文案组合),基于创意历史 CTR 和用户偏好进行动态选择。
竞价计费 t=55ms
按 GSP(广义第二价格)或 VCG 机制确定实际扣费金额。客户端渲染广告并上报曝光。
数据回传 t=55ms ~ 分钟级
曝光/点击/转化数据通过 Kafka 实时管道进入 Flink 计算引擎,更新预算余额、频控计数、计费、模型特征。
🔑 关键约束
| 约束 | 目标值 | 超时策略 |
|---|---|---|
| 端到端延迟 | < 100ms(P99) | 超时即降级,返回兜底广告或空 |
| 单步超时 | 召回 5ms / 精排 30ms / 总链路 80ms | 每步独立超时,失败不阻塞整体 |
| 降级策略 | 模型不可用时用规则打分 | 预置兜底广告列表,确保有广告返回 |
定向与召回
多级定向体系
召回策略对比
| 策略 | 技术实现 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| 倒排索引 | ElasticSearch / 自研倒排 | 精确匹配、速度快 | 泛化能力弱 | 定向标签匹配 |
| 向量召回 | Milvus / Faiss ANN | 语义理解、长尾发现 | 计算成本高、冷启动 | 内容推荐、兴趣拓展 |
| 规则召回 | 预配置规则引擎 | 简单可控 | 无法自适应 | 新品推广、保量广告 |
| 热门召回 | 统计 Top CTR/CVR | 保底效果 | 马太效应 | 冷启动、降级兜底 |
| 协同过滤 | UserCF / ItemCF | 群体智慧 | 稀疏性问题 | 成熟期广告 |
竞价与排序
拍卖机制
1. GSP(广义第二价格拍卖)— 业界主流
广告按 eCPM 降序排列,实际扣费 = 下一名的 eCPM / 自己的 CTR + 最小货币单位(0.01 元)。
公式:实际扣费 = eCPMnext / CTRself + 0.01
2. VCG(Vickrey-Clarke-Groves)拍卖
每个广告主支付其参与造成的「社会福利损失」。理论上最优,但计算复杂、广告主难以理解,仅在部分平台使用。
3. 第一价格拍卖
直接按出价扣费。简单透明,但广告主会策略性低报,导致平台收益下降。
| 机制 | 扣费规则 | 优势 | 劣势 | 采用率 |
|---|---|---|---|---|
| GSP | 按第二名价格扣费 | 鼓励真实出价、收益稳定 | 非理论最优 | ⭐⭐⭐⭐⭐ 最广泛 |
| VCG | 按社会福利损失扣费 | 理论最优 | 难以理解、计算复杂 | ⭐⭐ 较少 |
| 第一价格 | 按出价直接扣费 | 简单透明 | 策略性报价、收益波动 | ⭐⭐⭐ 增长中 (Header Bidding) |
排序模型演进
| 阶段 | 模型 | 特点 | 复杂度 |
|---|---|---|---|
| 第一代 | LR(逻辑回归) | 简单线性、可解释性强 | 低 |
| 第二代 | GBDT + LR | 自动特征组合、非线性 | 中 |
| 第三代 | DeepFM / Wide&Deep | 端到端学习、高阶特征交互 | 高 |
| 第四代 | DIN / DIEN / DLRM | 用户行为序列建模、注意力机制 | 很高 |
| 第五代 | 多目标 (MMOE / PLE) | 同时优化 CTR + CVR + 时长 | 极高 |
eCPM 计算公式
预算控制与消耗平滑
核心问题
广告主设定日预算(如 10,000 元/天),系统需要确保:
- 不超投:实际消耗 ≤ 设定预算
- 平滑消耗:预算均匀分布在全天,避免前 1 小时花完
- 效果最优:在预算约束下最大化转化或 ROI
预算控制策略对比
| 策略 | 原理 | 优势 | 劣势 |
|---|---|---|---|
| 匀速投放 (Even Pacing) |
将日预算按时间均匀分配,每小时预算 = 总预算 / 24 | 简单、可预测 | 忽略流量质量波动,优质流量时段无法多投 |
| 加速投放 (Accelerated) |
不考虑平滑,尽快消耗预算 | 抓住高峰流量 | 容易提前花完,ROI 波动大 |
| 概率节流 (Probabilistic Throttling) |
以概率 P = 剩余预算 / 预期消耗 决定是否参与竞价 | 平滑性好、实现简单 | 预估偏差影响效果 |
| PID 控制 | 将消耗偏差作为误差信号,PID 调节出价系数 | 自适应调节、平滑性最优 | 参数调优复杂 |
| 竞价概率法 (Bid Shading) |
根据实时胜率动态调整出价系数,预算充足时提高、紧张时降低 | 兼顾效果与平滑 | 需要准确预估胜率 |
| Smart Pacing | 结合流量质量预测和剩余预算,在高质量流量上提高竞价概率 | 预算利用率高、ROI 优 | 系统复杂度高 |
概率节流算法(代码示例)
频次控制 (Frequency Capping)
为什么需要频次控制
- 用户体验:同一广告重复曝光超过 5~7 次后,点击率断崖式下降,用户产生厌烦
- 广告主利益:过度曝光造成预算浪费,边际收益递减
- 平台生态:避免用户因广告疲劳而流失
频控维度设计
| 维度 | Key 设计 | 统计粒度 | 典型限制 |
|---|---|---|---|
| 用户-广告 | user_id:adgroup_id | 天/小时 | 同一广告每天最多 3 次 |
| 用户-广告主 | user_id:advertiser_id | 天/周 | 同一广告主每天最多 5 次 |
| 用户-类目 | user_id:category_id | 天 | 同一类目每小时最多 2 次 |
| 用户全局 | user_id:global | 分钟/小时 | 用户每小时最多 10 次广告 |
| IP-设备 | ip:device_hash | 天 | 防刷单设备高频曝光 |
实现方案
方案一:Redis 原子计数器(推荐)
方案二:布隆过滤器 + 计数器(大规模场景)
当用户量和广告量极大时,使用 Redis 的 Bloom Filter 减少内存开销,用 HyperLogLog 近似去重计数。
| 方案 | 内存占用 (1亿用户) | 精确度 | 适用场景 |
|---|---|---|---|
| Redis Hash | ~50GB | 精确 | 中小规模 (<10亿 key) |
| Bloom Filter + HLL | ~5GB | 近似 (误差 <1%) | 超大规模 (>10亿 key) |
| 本地内存 (LRU) | ~200MB/实例 | 近似 | 本地缓存加速层 |
实时数据管道
数据流架构
关键数据处理任务
| 任务 | 处理引擎 | 延迟要求 | 输出目标 |
|---|---|---|---|
| 实时计费 | Flink (Exactly-Once) | < 5s | Redis (预算余额) |
| 频控更新 | Flink (At-Least-Once) | < 1s | Redis (频控计数) |
| 反作弊检测 | Flink CEP | < 10s | 实时拦截 / 标记 |
| 特征计算 | Flink + Feature Store | < 30s | Redis / Feast |
| 模型样本拼接 | Flink / Spark | 分钟 ~ 小时级 | Hive / HDFS |
| 离线报表 | Spark SQL | T+1 | Hive / ClickHouse |
反作弊体系
广告作弊分类
| 类型 | 手法 | 危害 | 检测难度 |
|---|---|---|---|
| 流量作弊 (SIVT) | 机器刷量、脚本自动请求、代理IP池 | 虚增曝光,浪费广告主预算 | ⭐⭐ 中等 |
| 点击作弊 | 点击农场、自动点击脚本、诱导误点 | 虚增点击,抬高 CPC | ⭐⭐⭐ 较高 |
| 归因作弊 | Click Flooding、Click Injection、SDK Spoofing | 窃取自然转化归因 | ⭐⭐⭐⭐ 高 |
| SDK 破解 | 反编译、伪造上报参数、篡改设备信息 | 数据污染,定向失效 | ⭐⭐⭐⭐⭐ 极高 |
反作弊技术体系
| 层级 | 技术手段 | 检测维度 | 策略 |
|---|---|---|---|
| 规则层 | 黑名单 + 白名单 + 频率阈值 | IP、DeviceID、UA、Referer | 实时拦截异常请求 |
| 统计层 | 分布异常检测、时序异常检测 | CTR 突增、时段分布、地域集中度 | 离线分析 + 实时告警 |
| 机器学习层 | 孤立森林、AutoEncoder、GBDT 分类 | 多维度特征融合 | 离线模型 + 在线打分 |
| 行为层 | 用户行为序列分析、交互深度检测 | 页面停留、滑动轨迹、转化间隔 | 人机识别、行为验证 |
| 设备指纹层 | 设备指纹 SDK、环境风险检测 | 越狱/Root、模拟器、VPN/代理 | 前端 SDK 实时上报 |
归因反作弊 — 重点难题
Click Flooding(点击泛洪)
作弊方在短时间内对大量设备发起虚假点击,利用最后点击归因模型窃取自然转化。
防御:检测点击时间分布异常;限制单设备短时间内的归因窗口;引入多点归因模型。
Click Injection(点击注入)
作弊 App 监听系统广播(如 App 安装完成),在安装瞬间伪造一个"点击",抢先归因。
防御:使用 Referrer API 而非广播监听;检测安装与点击时间间隔的合理性。
SDK Spoofing(SDK 伪造)
不集成真实 SDK,直接伪造 HTTP 请求模拟点击/转化上报。
防御:请求签名 + Token 校验;设备指纹一致性校验;安装包验证。
常见问题与解决方案
问题 1:冷启动 — 新广告/新用户无历史数据
问题描述:新广告没有曝光和点击数据,CTR/CVR 预估不准,难以获得优质流量。新用户没有行为数据,画像为空,定向失准。
| 场景 | 解决方案 | 细节 |
|---|---|---|
| 新广告 | Exploration-Exploitation(E&E) | 分配 10~20% 流量用于探索,观察 CTR/CVR 快速收敛;用相似广告的 Embedding 作为冷启动特征 |
| 新广告 | 广告质量预评估 | 基于创意素材(图片 OCR、视频首帧)用 CV 模型预估质量分,给新广告一个合理的初始 eCPM |
| 新用户 | 设备特征推断 | 基于设备型号、价格、OS 推断消费水平和兴趣倾向,作为画像代理特征 |
| 新用户 | 热门内容兜底 | 在前 N 次曝光中使用全局热门/高质量广告替代个性化推荐 |
问题 2:数据延迟 — 预算/频控不一致
问题描述:曝光上报延迟导致预算余额显示不准确,可能出现超投。高并发下 Redis 更新与实际扣费存在时间差。
| 方案 | 实现 | 适用场景 |
|---|---|---|
| 预估预扣 | 竞价成功时在 Redis 中 预估扣减,曝光确认后精确修正 | CPM/CPC 竞价 |
| 双写机制 | 竞价成功时同步写 Redis(实时)和 Kafka(可靠),异步对账 | 所有计费模式 |
| 预算 Buffer | 设置 3~5% 的预算缓冲,实际限额 = 设定预算 × 95% | 日预算控制 |
| WAL 日志 | 每次预算扣减先写 Write-Ahead Log,保证崩溃恢复时数据不丢失 | 金融级计费场景 |
问题 3:超时雪崩 — 单点超时拖垮整条链路
问题描述:精排模型服务 GC 停顿或网络抖动导致一次请求超时,由于线程池阻塞,连锁反应导致整个 Ad Server 不可用。
| 方案 | 实现 |
|---|---|
| 断路器 (Circuit Breaker) | 当某个下游服务错误率超过阈值(如 50%),自动熔断,快速失败返回降级结果。定期半开探测恢复 |
| 独立线程池 | 每个下游调用使用独立线程池 + 独立超时时间,避免相互影响 |
| 超时传递 | 上游设置总超时 80ms,每一步从剩余时间中分配子超时(Deadline Propagation) |
| 降级策略 | 模型不可用时降级为规则打分(如最近 7 天平均 CTR × bid),保证链路不中断 |
| 预热机制 | 服务启动时预热模型和缓存,避免冷启动超时 |
问题 4:模型更新与线上一致性
问题描述:模型离线训练完成后,线上 Serving 如果直接加载新模型,可能导致预估跳变。如果训练样本和线上特征不一致,模型效果衰减。
| 方案 | 实现 |
|---|---|
| 灰度发布 | 新模型先在 5% 流量验证效果,逐步扩大到 50% → 100% |
| 特征快照 | 离线训练时使用与线上完全一致的特征快照,避免 Training-Serving Skew |
| 模型版本管理 | 保留最近 N 个模型版本,支持一键回滚 |
| A/B 实验框架 | 通过实验平台分流,新模型与旧模型并行运行,基于统计显著性决策是否全量 |
问题 5:高并发下的数据一致性
问题描述:千万 QPS 场景下,预算扣减、频控更新等写操作会产生热点 Key 问题,导致 Redis 单分片过载。
| 方案 | 实现 |
|---|---|
| 本地计数器 + 异步合并 | 先在实例本地内存中累加,每 1 秒批量同步到 Redis,减少 Redis 写入压力 10~100 倍 |
| 分桶 (Sharding) | 将单个广告预算拆分为 N 个虚拟分桶,分散热点。如 budget:ad_123 拆为 budget:ad_123:0 ~ budget:ad_123:9 |
| Lua 脚本原子操作 | 将「检查 + 扣减」封装为 Redis Lua 脚本,保证原子性 |
| 最终一致性 | 对于频控等非金融级场景,接受秒级的最终一致性,降低系统复杂度 |
问题 6:多目标优化的 "跷跷板" 效应
问题描述:同时优化 CTR(用户体验)、CVR(广告主 ROI)和 eCPM(平台收益)时,三者存在 trade-off。单纯提高 eCPM 可能导致广告相关性下降。
| 方案 | 实现 |
|---|---|
| 多目标建模 (MMOE/PLE) | 共享底层网络,每个目标有独立的 Expert 子网络和 Tower,支持动态权重调整 |
| 约束优化 | 在满足最低 CTR/CVR 约束的前提下最大化 eCPM。如:max eCPM s.t. CTR > 2% |
| 融合公式 | score = α×eCPM + β×pCTR + γ×diversity。通过 α/β/γ 调参平衡各目标 |
| 强化学习 (RL) | 将广告投放建模为序列决策问题,用 RL 优化长期累积收益而非单次收益 |
问题 7:广告素材违规与审核
问题描述:广告素材可能包含违规内容(色情、虚假宣传、侵权等),需要在上线前审核。人工审核效率低,自动化审核有漏判风险。
| 方案 | 实现 |
|---|---|
| 机审 + 人审双层 | CV+NLP 模型自动审核(覆盖 90% 明显违规),高风险素材送人工复审 |
| 多模型融合 | 图片审核(ResNet/EfficientNet)+ 文字审核(BERT)+ OCR 提取,降低漏判率 |
| 实时在线审核 | 广告上线后持续抽样审核,违规素材 5 分钟内自动下架 |
性能与高可用设计
性能优化策略
| 维度 | 策略 | 效果 |
|---|---|---|
| 缓存体系 | L1 本地缓存 (Caffeine) → L2 Redis → L3 DB;热点广告 80% 命中 L1 | 延迟降低 10~20ms |
| 并行化 | 多路召回并行执行;CTR/CVR 预估并行调用;特征计算并行拉取 | 延迟降低 50% |
| 异步化 | 曝光/点击上报异步发送 Kafka;预算扣减异步批处理 | 主链路延迟降低 15ms |
| 预计算 | 用户画像、广告特征离线预计算,在线只做轻量拼接 | 特征获取从 10ms → 1ms |
| 数据压缩 | Protobuf 序列化替代 JSON;Redis 使用 Snappy 压缩 | 带宽降低 60% |
| 连接池 | Redis/模型服务使用长连接池,避免 TCP 握手开销 | 连接建立耗时从 3ms → 0 |
| 淘汰低质广告 | 召回阶段过滤连续 100 次曝光无点击的广告 | 排序候选量减少 30% |
高可用架构
| 机制 | 方案 | RTO |
|---|---|---|
| 多活部署 | 至少 3 个机房(或可用区)同时提供服务,流量按比例分发 | < 30s |
| 自动扩缩容 | 基于 CPU/QPS 指标的 HPA(K8s),提前预留 30% 余量 | < 60s |
| 异地容灾 | 主备数据中心,数据库主从同步延迟 < 1s | < 5min |
| 熔断降级 | Hystrix / Sentinel 实现服务熔断,降级链路保证核心功能 | 实时 |
| 流量隔离 | 核心链路(Ad Serving)与离线任务(报表)物理隔离,互不影响 | — |
| 全链路压测 | 每周模拟 2x 峰值流量压测,发现瓶颈提前扩容 | — |
方案对比:自建 vs 第三方 vs 混合
三种方案的适用场景
| 维度 | 自建广告系统 | 第三方聚合平台 | 混合方案 |
|---|---|---|---|
| 初期投入 | 极高 需要 20+ 人团队 | 低 SDK 集成即可 | 中等 |
| 数据控制权 | 完全自主 | 依赖第三方 | 部分自主 |
| 利润率 | 最高(无平台抽佣) | 最低(抽佣 20~50%) | 中等 |
| 广告主资源 | 需自建 BD 团队 | 海量广告主 | 多元化 |
| 技术门槛 | 极高 | 低 | 中等 |
| 策略灵活性 | 完全可控 | 受平台限制 | 较高 |
| 适合场景 | 日活千万+ 头部平台 | 中小 App、创业初期 | 日活百万+ 成长中平台 |
| 典型案例 | 字节跳动(穿山甲外)、腾讯广告、Meta | AdMob、Unity Ads、AppLovin | 哔哩哔哩、知乎 |
技术选型推荐
| 组件 | 推荐方案 | 备选 | 选型理由 |
|---|---|---|---|
| 编程语言 | C++ / Go | Rust / Java | 极致性能 + 低 GC 开销 |
| 消息队列 | Apache Kafka | Pulsar | 高吞吐、持久化、生态成熟 |
| 实时计算 | Apache Flink | Spark Streaming | Exactly-Once 语义、低延迟 |
| 在线缓存 | Redis Cluster | Dragonfly / KeyDB | 高性能、丰富数据结构 |
| 搜索引擎 | Elasticsearch | 自研倒排 | 全文检索、聚合分析 |
| 向量检索 | Milvus / Faiss | Qdrant | 十亿级向量检索 |
| 模型 Serving | Triton + TF Serving | TorchServe | GPU 优化、多框架支持 |
| OLAP | ClickHouse | Doris / StarRocks | 亚秒级实时分析查询 |
| 离线数仓 | Hive / Iceberg | Delta Lake | PB 级数据存储和 ETL |
💡 核心设计原则:
快速失败 > 缓慢响应
最终一致 > 强一致阻塞
降级可用 > 完美崩溃
数据驱动 > 经验拍板