广告系统设计

一份从系统架构到工程实践的完整技术指南,涵盖广告投放全链路、核心算法机制、常见工程问题及解决方案。

<100ms
端到端延迟
百万级 QPS
广告请求量
毫秒级
竞价决策时间
99.99%
可用性目标

什么是广告系统

广告系统是连接 广告主(需求方)、媒体(供给方)和 用户 的三方平台。其核心目标是在正确的时间、正确的场景,将正确的广告展示给正确的用户,同时实现广告主 ROI 最大化、媒体收益最大化和用户体验最优的三角平衡。

核心公式:eCPM = CPC × CTR × 1000 = CPA × CVR × CTR × 1000

关键业务指标

指标全称含义
CPMCost Per Mille千次展示成本
CPCCost Per Click单次点击成本
CPACost Per Action单次转化成本
CTRClick-Through Rate点击率 = 点击数 / 展示数
CVRConversion Rate转化率 = 转化数 / 点击数
eCPMEffective CPM有效千次展示收益
Fill Rate填充率有广告返回的请求 / 总请求
Win Rate竞价胜率竞价成功次数 / 参与竞价次数

业务参与者

角色核心诉求关注指标
广告主精准触达目标用户,控制成本,最大化 ROICPA、ROAS(广告支出回报率)、转化成本
媒体充分售卖流量,提升收益,不影响用户体验eCPM、Fill Rate、ARPU
用户看到相关广告,不被过度打扰广告相关性、展示频率
平台撮合供需,技术支撑,数据驱动优化GMV、平台抽佣率、系统稳定性

核心架构

整体架构分层

接入层 Ad Server (Nginx / OpenResty) HTTPS 接入 · 协议解析 · 参数校验 · 流量染色 · 限流熔断 广告引擎 Ad Engine (核心决策) 召回 Retrieval 粗排 Pre-Ranking 精排 Ranking 重排 Re-Ranking 数据 & 策略层 用户画像 DMP · 广告索引 · 频控/预算状态 · AB实验 Redis Cluster · ElasticSearch · 向量检索 Milvus 模型服务层 CTR 预估 · CVR 预估 · eCPM 计算 · 创意优选 TensorFlow Serving · Triton · 特征平台 数据管道 Data Pipeline Kafka → Flink 实时计算(曝光/点击/转化归因) → ClickHouse / Hive 离线数仓 实时计费 · 反作弊检测 · 报表生成 · 模型训练样本拼接 可观测性:Prometheus + Grafana 监控 · ELK 日志 · Jaeger 链路追踪 · 告警 + 自愈 发布系统:灰度发布 · 分机房部署 · 多活容灾

各层职责详解

层级核心职责关键技术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 以内完成。

链路流程

1

用户请求触发 t=0ms

用户滑动信息流 / 打开页面时,客户端 SDK 向 Ad Server 发送广告请求,携带 device_id、广告位 ID、上下文信息(内容标签、地理位置等)。

2

流量接入与校验 t=2ms

网关层完成协议解析、参数校验、流量染色标记。对异常流量(黑名单 IP、高频请求)进行识别和拒绝。

3

多路召回 t=5ms

从多个维度并行检索候选广告:用户定向匹配(倒排索引)、向量相似召回(Embedding)、热门/兜底广告。从百万级广告库中召回数千个候选。

4

预算 & 频控过滤 t=8ms

过滤预算耗尽、频次达到上限、不在投放时段的广告。读取 Redis 中的实时预算状态和用户维度频控计数器。

5

粗排(Pre-Ranking) t=15ms

用轻量级模型对数千个候选广告快速打分,截断保留 Top 200~500。关注计算效率,允许一定的精度损失。

6

精排(Ranking) t=35ms

使用深度模型(DeepFM / DCN / DIN)对每个广告进行 CTR 和 CVR 预估。结合广告主出价计算 eCPM = bid × pCTR × pCVR × 1000。按 eCPM 降序排列。

7

重排(Re-Ranking) t=45ms

考虑用户体验约束:同类目打散、信息流中广告位置间隔控制。引入多样性、新鲜度因子,避免用户疲劳。

8

创意优选 t=50ms

对胜出广告选择最优创意(图片、视频、文案组合),基于创意历史 CTR 和用户偏好进行动态选择。

9

竞价计费 t=55ms

按 GSP(广义第二价格)或 VCG 机制确定实际扣费金额。客户端渲染广告并上报曝光。

10

数据回传 t=55ms ~ 分钟级

曝光/点击/转化数据通过 Kafka 实时管道进入 Flink 计算引擎,更新预算余额、频控计数、计费、模型特征。

🔑 关键约束

约束目标值超时策略
端到端延迟< 100ms(P99)超时即降级,返回兜底广告或空
单步超时召回 5ms / 精排 30ms / 总链路 80ms每步独立超时,失败不阻塞整体
降级策略模型不可用时用规则打分预置兜底广告列表,确保有广告返回

定向与召回

多级定向体系

人口属性定向 • 年龄区间 • 性别 • 地域 (LBS) • 设备型号/OS • 网络环境 • 消费水平 定位:基础过滤 候选量缩减 50~80% 兴趣 & 行为定向 • 兴趣标签 (IAB) • 浏览历史 • 搜索关键词 • 购买/加购行为 • App 使用行为 • 内容偏好 定位:精准匹配 候选量缩减 60~90% 上下文定向 • 页面内容分类 • 关键词匹配 • 媒体/频道 • 广告位类型 • 时段定向 • 天气/事件定向 定位:场景匹配 候选量缩减 40~70% 向量相似召回 • User Embedding • Ad Embedding • 双塔模型 (DSSM) • ANN 近似检索 • 多兴趣向量 • 实时向量更新 定位:长尾发现 Top-K 1000+ 候选

召回策略对比

策略技术实现优势劣势适用场景
倒排索引 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 计算公式

// 按 CPC 出价 eCPM = bid_cpc × pCTR × 1000 // 按 CPA 出价 eCPM = bid_cpa × pCTR × pCVR × 1000 // 按 CPM 出价 eCPM = bid_cpm // 混合出价(最优策略) eCPM = bid_cpm + bid_cpc × pCTR × 1000 + bid_cpa × pCTR × pCVR × 1000 // 引入质量分 eCPM = bid × pCTR × pCVR × quality_score × 1000

预算控制与消耗平滑

核心问题

广告主设定日预算(如 10,000 元/天),系统需要确保:

  • 不超投:实际消耗 ≤ 设定预算
  • 平滑消耗:预算均匀分布在全天,避免前 1 小时花完
  • 效果最优:在预算约束下最大化转化或 ROI

预算控制策略对比

策略原理优势劣势
匀速投放
(Even Pacing)
将日预算按时间均匀分配,每小时预算 = 总预算 / 24 简单、可预测 忽略流量质量波动,优质流量时段无法多投
加速投放
(Accelerated)
不考虑平滑,尽快消耗预算 抓住高峰流量 容易提前花完,ROI 波动大
概率节流
(Probabilistic Throttling)
以概率 P = 剩余预算 / 预期消耗 决定是否参与竞价 平滑性好、实现简单 预估偏差影响效果
PID 控制 将消耗偏差作为误差信号,PID 调节出价系数 自适应调节、平滑性最优 参数调优复杂
竞价概率法
(Bid Shading)
根据实时胜率动态调整出价系数,预算充足时提高、紧张时降低 兼顾效果与平滑 需要准确预估胜率
Smart Pacing 结合流量质量预测和剩余预算,在高质量流量上提高竞价概率 预算利用率高、ROI 优 系统复杂度高

概率节流算法(代码示例)

// 每个时间片(如每分钟)更新参与概率 def calc_participation_prob(budget_left, budget_total, time_left, time_total): // 预期剩余消耗速率(元/分钟) expected_spend_rate = budget_left / time_left // 当前实际消耗速率(元/分钟) actual_spend_rate = get_recent_spend_rate(last_5_minutes) if actual_spend_rate > expected_spend_rate * 1.2: // 消耗过快,降低竞价概率 return expected_spend_rate / actual_spend_rate elif actual_spend_rate < expected_spend_rate * 0.8: // 消耗过慢,提高竞价概率(上限 1.0) return min(1.0, expected_spend_rate / actual_spend_rate) else: return 1.0 // Redis 原子预算扣减 def deduct_budget(campaign_id, amount): key = f"budget:{campaign_id}:{today}" // Lua 脚本保证原子性 lua_script = """ local current = redis.call('GET', KEYS[1]) or 0 local budget_limit = redis.call('GET', KEYS[2]) if current + ARGV[1] > budget_limit then return -1 -- 预算不足 end redis.call('INCRBY', KEYS[1], ARGV[1]) return current + ARGV[1] """ return redis.eval(lua_script, 2, key, limit_key, amount)

频次控制 (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 Hash + TTL 实现多维度频控 HINCRBY user_freq:{user_id}:{date} {adgroup_id} 1 EXPIRE user_freq:{user_id}:{date} 86400 // 24小时过期 // 读取频控状态(一次 HMGET 获取所有维度) freq_counts = redis.hmget(f"user_freq:{user_id}:{date}", ad_id, adv_id, cat_id) for count, limit in zip(freq_counts, limits): if count and int(count) >= limit: skip_ad() // 超过频次,过滤此广告

方案二:布隆过滤器 + 计数器(大规模场景)

当用户量和广告量极大时,使用 Redis 的 Bloom Filter 减少内存开销,用 HyperLogLog 近似去重计数。

方案内存占用 (1亿用户)精确度适用场景
Redis Hash~50GB精确中小规模 (<10亿 key)
Bloom Filter + HLL~5GB近似 (误差 <1%)超大规模 (>10亿 key)
本地内存 (LRU)~200MB/实例近似本地缓存加速层

实时数据管道

数据流架构

数据采集 — 客户端 SDK / 服务端埋点 曝光上报 · 点击上报 · 转化回传 (App Attribution / Pixel) · 播放进度 · 滑动深度 消息队列 — Apache Kafka (多 Topic 分区) Topic: ad_impression / ad_click / ad_conversion / ad_billing · 百万条/秒吞吐 Flink 实时计算 • 实时计费扣减 • 频控计数更新 • 反作弊检测 Flink 特征工程 • 实时特征计算 • 模型特征回写 • 样本拼接 (Join) Flink 实时报表 • 实时大盘 (曝光/点击/消耗) • 广告主实时数据 • 异常告警 在线存储 — Redis Cluster · HBase/Cassandra 实时状态:预算余额、频控计数、用户画像 · 低延迟 (<5ms) 离线存储 — Hive / ClickHouse / Iceberg 数据仓库:7~90 天报表、模型训练样本、用户画像全量 离线模型训练 (Spark ML / TF) → 模型热加载到在线 Serving → 持续迭代

关键数据处理任务

任务处理引擎延迟要求输出目标
实时计费Flink (Exactly-Once)< 5sRedis (预算余额)
频控更新Flink (At-Least-Once)< 1sRedis (频控计数)
反作弊检测Flink CEP< 10s实时拦截 / 标记
特征计算Flink + Feature Store< 30sRedis / Feast
模型样本拼接Flink / Spark分钟 ~ 小时级Hive / HDFS
离线报表Spark SQLT+1Hive / 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++ / GoRust / Java极致性能 + 低 GC 开销
消息队列Apache KafkaPulsar高吞吐、持久化、生态成熟
实时计算Apache FlinkSpark StreamingExactly-Once 语义、低延迟
在线缓存Redis ClusterDragonfly / KeyDB高性能、丰富数据结构
搜索引擎Elasticsearch自研倒排全文检索、聚合分析
向量检索Milvus / FaissQdrant十亿级向量检索
模型 ServingTriton + TF ServingTorchServeGPU 优化、多框架支持
OLAPClickHouseDoris / StarRocks亚秒级实时分析查询
离线数仓Hive / IcebergDelta LakePB 级数据存储和 ETL

💡 核心设计原则
快速失败 > 缓慢响应
最终一致 > 强一致阻塞
降级可用 > 完美崩溃
数据驱动 > 经验拍板