2. 链路成本模型
上一章我们聊了为什么要做成本追踪。这一章,咱们得把「成本」这个词拆开看看。
说白了,一个特征从原始数据变成线上可用的实时特征,到底花掉了哪些资源?我个人的习惯是,先把成本分成三大块:计算成本、存储成本、传输成本。这三块几乎能覆盖所有场景。
你想想看,任何一个特征链路,无非就是「算一下」、「存一下」、「传一下」。对吧?
计算成本:CPU 和内存的账本
计算成本是最直观的。特征工程里那些聚合、转换、JOIN,哪个不要消耗 CPU 和内存?
我在项目中遇到过一种情况:团队上线了一个实时滑动窗口计数特征,逻辑很简单,但上线后 CPU 直接飙到 90%。后来一查,窗口大小是 7 天,数据量又大,每次触发都要重新扫描历史数据。这就是典型的计算成本失控。
我个人习惯把计算成本拆成两个维度:
- 单次计算成本:处理一条事件需要多少 CPU 时间、多少内存分配。
- 计算频率:这个特征每秒、每分钟被触发多少次。
两者相乘,就是总计算成本。嗯,这里要注意,频率往往比单次成本更隐蔽。单次成本再低,频率一高,总量照样吓人。
核心公式:
计算成本 = Σ (单次计算耗时 × 计算频率) × 单位时间
举个例子,一个简单的特征提取函数:
def extract_user_features(event):
# 假设这是一个简单的特征提取
user_id = event['user_id']
# 从 Redis 读取用户画像
profile = redis_client.get(f"profile:{user_id}")
# 做一些简单的数值计算
score = profile['base_score'] * event['amount']
return score
这段代码看起来简单,但每次调用都要读一次 Redis。如果每秒有 10 万次调用,那 Redis 的 QPS 和网络开销就成了大头。我曾经见过一个团队,就是因为没算 Redis 的调用成本,结果线上 Redis 被打挂了。
存储成本:数据躺着也要花钱
存储成本,说白了就是数据「躺着」的时候花的钱。很多人只关注计算,忽略了存储。但实时特征链路里,存储往往是长期的大头。
我建议从三个角度去衡量:
- 存储介质:内存(Redis、Memcached)、SSD(本地磁盘)、云存储(S3、HDFS)。价格差好几个数量级。
- 数据量:每个特征占多少字节,乘以副本数,乘以存储时长。
- TTL 策略:数据存多久?过期后是否自动清理?
| 存储介质 | 典型延迟 | 每 GB 月成本(估算) | 适用场景 |
|---|---|---|---|
| 内存(Redis) | < 1ms | ¥200 - ¥500 | 高频实时特征 |
| SSD(本地) | 1-10ms | ¥10 - ¥30 | 中等频率特征 |
| 云存储(S3) | 100ms+ | ¥1 - ¥5 | 冷数据、历史回溯 |
你看,内存和云存储的成本差了上百倍。所以,我经常跟团队说:能放磁盘的,别放内存;能放冷存的,别放热存。这不是废话,是实打实的钱。
避坑指南: 我曾经接手过一个项目,所有特征都放在 Redis 里,TTL 设成了 30 天。结果 Redis 集群内存占用 80%,每月成本好几万。后来一分析,80% 的特征其实只需要存 1 小时。改成按特征级别设置 TTL 后,成本直接降了 60%。
传输成本:数据在路上的开销
传输成本是最容易被忽视的。数据从 A 到 B,看起来只是「发个包」,但背后的网络带宽、序列化/反序列化、连接数,都是钱。
我习惯把传输成本分成三类:
- 网络带宽:每秒传输的数据量。带宽不够就丢包、重传,成本飙升。
- 序列化/反序列化:JSON、Protobuf、Avro,不同格式的 CPU 开销和体积差异很大。
- 连接管理:长连接 vs 短连接,连接池大小,握手开销。
举个例子,同样是传输一个用户特征:
// JSON 格式:可读性好,但体积大
{"user_id": "12345", "score": 0.85, "timestamp": 1700000000}
// Protobuf 格式:体积小,但需要 schema
message UserFeature {
string user_id = 1;
float score = 2;
int64 timestamp = 3;
}
JSON 比 Protobuf 体积大 3-5 倍。如果每秒传输 10 万条,带宽成本就差好几倍。我在项目中做过一次对比,把核心链路的序列化格式从 JSON 改成 Protobuf 后,带宽成本降了 70%。
注意: 传输成本不只是带宽。序列化/反序列化本身也消耗 CPU。有时候为了省带宽,用了复杂的压缩算法,结果 CPU 先扛不住了。要权衡。
三种成本的联动关系
这三种成本不是孤立的。它们经常互相影响。比如:
- 为了降低存储成本,把数据压缩了 → 读取时解压缩消耗 CPU → 计算成本上升。
- 为了降低传输成本,用了更紧凑的序列化格式 → 写入和读取时序列化/反序列化开销增加 → 计算成本上升。
- 为了降低计算成本,把中间结果缓存到内存 → 存储成本上升。
你看,这就是一个跷跷板。我个人的经验是:不要追求单一成本最低,要追求总成本最优。具体怎么权衡,后面几章会详细讲。
下面这张图,是我自己画的一个成本模型框架,帮你快速理解三者的关系:
如何量化这三种成本?
光说概念没用,得能算。我分享一个我自己在项目中用的量化方法:
- 计算成本量化:用 profiling 工具(比如 JFR、perf)跑一次特征处理,拿到 CPU 耗时和内存分配量。然后乘以线上 QPS。单位:CPU 毫秒/秒、MB 内存/秒。
- 存储成本量化:统计每个特征在存储中的实际占用字节数,乘以副本数,再乘以存储时长。单位:GB·天。
- 传输成本量化:统计每条消息的字节数,乘以每秒消息量,得到带宽占用。单位:Mbps。
一个小技巧: 我习惯在代码里埋一个「成本探针」,把每次特征处理的 CPU 时间、内存分配、序列化后大小都打出来。这样上线前就能看到成本预估,而不是等线上出问题再查。