广告竞价为什么要拼毫秒级速度?揭秘 RTB 实时广告系统背后的数据流水线设计
作者:Echo_Wish
方向:大数据架构 / 实时计算 / AI系统设计
你有没有想过:
打开一个网页,短短几百毫秒后,一张广告图片已经精准出现在你面前。
你看到的是一张广告。
但在广告平台眼里,这背后可能刚刚经历了一场“百亿级别的小型战争”。
用户点击页面 → 浏览器发起广告请求 → 多家广告主参与竞价 → 系统预测点击概率 → 计算广告价值 → 完成竞价 → 返回广告素材。
整个过程通常要求:
100毫秒以内完成决策。
这就是 RTB(Real-Time Bidding,实时竞价)系统。
很多人认为广告系统就是“存数据 + 展示广告”,其实真正的大厂广告平台,本质上是一个:
实时数据流处理系统 + 机器学习预测系统 + 高性能交易系统。
今天我们聊聊,一个广告 RTB 平台背后的数据流水线应该怎么设计,以及为什么很多系统一上量就崩。
一、RTB到底是什么?一次广告展示就是一次实时交易
先简单理解一下。
比如你打开一个新闻网站:
页面有一个广告位。
网站:
“我这里有一个用户流量,现在出售。”
广告平台:
“这个用户价值多少钱?”
多个广告主:
“我要出价!”
最终:
最高价值广告获得展示机会。
整个过程类似股票交易。
区别是:
股票交易可能几秒。
广告交易:
几十毫秒。
因为用户不会等你。
所以 RTB 系统必须解决三个问题:
- 数据实时进入
- 数据实时计算
- 数据实时决策
二、RTB数据流水线整体架构
一个典型广告实时竞价系统,大概长这样:
用户请求
|
v
广告网关
|
v
实时竞价服务
|
+------------+
| |
v v
用户画像 广告库
| |
+------------+
|
v
竞价模型计算
|
v
返回广告
同时:
点击日志
曝光日志
交易日志
|
v
Kafka
|
v
实时计算 Flink
|
v
用户画像更新
模型训练
数据分析
这里最核心的是:
实时数据流。
三、第一道难题:广告数据为什么不能直接写数据库?
很多初学者设计系统:
用户点击广告:
↓
insert mysql
↓
分析数据
看起来没问题。
但是实际广告系统:
每天可能产生:
- 100亿曝光日志
- 10亿点击日志
- 千万级广告请求 QPS
如果全部写 MySQL:
数据库直接“冒烟”。
例如:
INSERT INTO ad_click_log
(
user_id,
ad_id,
click_time
)
VALUES
(
'10001',
'A001',
NOW()
);
一天几十亿次:
MySQL:
CPU 100%
IO等待
连接池耗尽
系统雪崩
所以广告系统第一原则:
日志不要直接进入业务数据库。
而是进入消息队列。
四、Kafka为什么成为广告系统标配?
广告系统通常第一站:
Kafka。
例如:
用户点击:
{
"userId":"U10001",
"adId":"AD9001",
"event":"click",
"timestamp":1720000000
}
发送:
from kafka import KafkaProducer
import json
producer = KafkaProducer(
bootstrap_servers=[
"localhost:9092"
],
value_serializer=lambda x:
json.dumps(x).encode()
)
event={
"userId":"U10001",
"adId":"AD9001",
"event":"click"
}
producer.send(
"ad_click_topic",
event
)
producer.flush()
Kafka负责:
- 削峰
- 解耦
- 缓冲
比如:
广告活动突然爆发:
平时:
10万QPS
突然:
100万QPS
Kafka:
生产速度:
100万/s
消费速度:
20万/s
剩余80万:
先进入队列
慢慢处理
不会直接击穿后端。
五、实时计算:为什么广告需要Flink?
广告系统最关心:
不是昨天的数据。
而是:
刚刚发生的数据。
例如:
一个用户:
过去5分钟:
搜索:
“新能源汽车”
浏览:
“特斯拉”
点击:
“电动车报价”
那么下一秒广告:
应该推荐:
新能源相关广告。
这个过程需要实时计算。
Flink代码示例:
DataStream<Event> stream =
env
.fromSource(
kafkaSource,
WatermarkStrategy.noWatermarks(),
"click"
);
stream
.keyBy(
Event::getUserId
)
.window(
SlidingEventTimeWindows
.of(
Time.minutes(5),
Time.minutes(1)
)
)
.aggregate(
new UserInterestAggregator()
)
.print();
含义:
按照用户ID分组。
统计最近5分钟行为。
实时更新用户兴趣。
例如:
用户画像:
之前:
{
"user":"U1001",
"interest":[
"手机"
]
}
实时变化:
{
"user":"U1001",
"interest":[
"手机",
"新能源汽车",
"智能家居"
]
}
六、RTB真正难点:毫秒级竞价计算
广告竞价不是简单:
谁价格高谁赢。
现在广告系统一般计算:
广告价值 =
出价
×
点击概率
×
转化概率
×
用户价值
例如:
广告A:
出价:
2元
点击概率:
5%
转化概率:
10%
价值:
2 × 0.05 × 0.1
=0.01
广告B:
出价:
1元
点击概率:
20%
转化概率:
30%
价值:
0.06
虽然B出价低。
但是系统选择B。
这就是:
智能广告。
七、机器学习模型如何进入RTB?
广告平台通常会训练:
CTR模型:
Click Through Rate
预测点击率。
例如:
输入:
用户年龄
地域
兴趣
历史行为
广告类型
时间
设备
输出:
点击概率
简单模型:
from sklearn.linear_model import LogisticRegression
model=LogisticRegression()
X=[
[25,1,3],
[40,0,2],
[30,1,5]
]
y=[
1,
0,
1
]
model.fit(
X,
y
)
prob=model.predict_proba(
[
[28,1,4]
]
)
print(prob)
输出:
点击概率:
0.73
广告系统根据:
预测概率 × 出价
决定排序。
八、性能优化:为什么广告系统喜欢内存计算?
RTB最大的敌人:
慢。
如果每次查询:
MySQL:
用户画像
广告信息
模型参数
查询:
10ms
20ms
30ms
加起来:
几十毫秒没了。
所以:
大量数据放:
Redis。
例如:
用户画像:
key:
user:10001
value:
{
age:30,
interest:[
AI,
汽车
]
}
查询:
import redis
r=redis.Redis(
host="localhost",
port=6379
)
profile=r.get(
"user:10001"
)
print(profile)
Redis:
微秒级响应。
九、数据冷热分离,是广告系统的生存技巧
广告数据非常特殊。
比如:
最近一分钟点击:
非常重要。
三年前点击:
基本没人看。
所以:
冷热分离。
热数据
存:
Redis
Flink State
特点:
高速访问。
温数据
存:
HBase
ClickHouse
例如:
最近30天广告效果。
冷数据
存:
HDFS
对象存储
用于:
模型训练。
架构:
实时数据
Kafka
|
Flink
|
Redis
|
实时决策
历史数据
HDFS
|
Spark
|
模型训练
十、真正的大规模RTB系统,优化重点在哪里?
我认为有三个关键。
第一:不要让数据库承担实时计算
数据库负责:
存储。
计算交给:
Flink/Spark。
第二:减少网络调用
RTB一次请求:
可能调用:
用户画像
广告库
模型服务
风控服务
如果:
10个服务 × 5ms
直接超时。
所以:
需要:
- 服务合并
- 本地缓存
- 模型预加载
第三:数据链路必须可观测
广告系统最怕:
“不知道哪里慢。”
所以需要:
监控:
Kafka lag
Flink checkpoint
接口耗时
模型响应时间
QPS
错误率
例如:
Prometheus:
- job_name:
rtb-service
metrics_path:
/metrics
Grafana展示:
RTB响应时间
P99:
85ms
Kafka延迟:
2000
模型耗时:
15ms
十一、写在最后:RTB其实就是大数据时代的“高速交易系统”
很多人学习大数据,只关注:
Hadoop
Spark
Hive
但真正工业级应用:
不是离线分析。
而是:
实时决策。
广告RTB只是其中一个代表。
类似架构还应用于:
- 推荐系统
- 风控系统
- 智能客服
- 自动驾驶
- 金融交易
它们都有共同特点:
数据不断产生。
系统不断计算。
决策必须实时。
未来的大数据竞争,不是谁存的数据多。
而是谁:
能够最快把数据变成行动。
这也是为什么:
实时计算、流式架构、AI模型服务,会成为未来数据工程师必须掌握的核心能力。
—— Echo_Wish
数据不会自动产生价值,真正产生价值的是:让数据在正确的时间,做出正确的决策。