数据采集层:前端埋点、服务端日志与消息队列

数据采集是整个用户行为流的起点。说白了,你后面分析得再花哨,如果采集层就漏了数据或者采错了,那全是白搭。我见过太多团队在实时计算上花了大价钱,最后发现是埋点埋歪了——嗯,这种坑踩一次就够了。

这一章我们聚焦三个核心问题:前端怎么采、后端怎么补、中间怎么传。我把它们拆成三块来讲。

2.1 前端埋点:代码埋点 vs 全埋点

前端埋点,就是用户在浏览器或 App 里操作时,我们把行为数据发回来。目前主流就两种方式:代码埋点和全埋点。我分别说说它们的适用场景和坑。

2.1.1 代码埋点

代码埋点是最传统的方式。开发人员在需要追踪的交互位置,手动插入一段上报代码。比如用户点击「加入购物车」按钮,你就写一行:

// 代码埋点示例
trackEvent('add_to_cart', {
  product_id: 'P10086',
  price: 199.00,
  quantity: 1,
  page: 'product_detail'
});

优点很明显:数据精确,你想采什么字段就采什么字段。缺点也直接:每次改埋点都要发版,而且容易漏埋。

我的习惯:核心业务路径(注册、下单、支付)必须用代码埋点。这些地方数据质量要求高,不能含糊。

2.1.2 全埋点

全埋点也叫无埋点或自动埋点。前端 SDK 自动监听所有用户交互事件——点击、滑动、页面浏览——然后一股脑全发回来。你不需要写任何业务代码。

听起来很爽对吧?但有个大问题:数据量爆炸。一个页面可能触发几十个事件,其中 80% 是你根本不需要的。而且全埋点拿不到业务语义——它只知道「用户点了坐标 (x,y) 处的元素」,不知道那是「加入购物车」按钮。

我曾经踩过的坑:有个项目全量上了全埋点,一天产生 5 亿条事件。结果 Kafka 集群直接被打满,下游实时任务大面积延迟。后来我强制要求:全埋点只用于「探索期」——新功能上线前两周开全埋点看用户行为模式,之后切回代码埋点。
对比维度 代码埋点 全埋点
数据精确度 高,字段可控 低,缺乏业务语义
开发成本 高,每次改埋点需发版 低,接入 SDK 即可
数据量 可控,只采需要的 巨大,大量冗余事件
适用场景 核心业务路径 探索期、A/B 测试

2.2 服务端日志采集

前端埋点有个天然缺陷:用户可能禁用 JavaScript、网络断连、或者直接关掉页面。这时候行为数据就丢了。服务端日志采集就是来补这个缺的。

服务端日志,说白了就是你的后端服务在处理每个请求时,把关键信息打印到日志文件里。比如用户下单,后端收到请求后记录:

// 服务端日志示例(JSON 格式)
{
  "timestamp": 1712563200000,
  "event": "order_created",
  "user_id": "U_12345",
  "order_id": "O_67890",
  "amount": 299.00,
  "ip": "192.168.1.1",
  "user_agent": "Mozilla/5.0..."
}

服务端日志的好处是:数据可靠,只要请求到了后端,日志就一定存在。而且你能拿到一些前端拿不到的信息,比如真实 IP、服务端处理耗时。

关键原则:前端埋点 + 服务端日志必须配合使用。前端负责「用户意图」,后端负责「业务结果」。比如用户点击下单(前端事件),后端确认订单创建成功(服务端日志),两者通过 event_id 关联,才能还原完整链路。

我个人习惯的做法是:前端埋点和服务端日志使用同一套 event_id 生成规则。这样在实时计算时,可以轻松做 join 操作,判断「点击了但没下单」的流失场景。

2.3 消息队列:Kafka 基础

数据采上来了,往哪放?直接写数据库?别闹。实时场景下,前端每秒可能产生几万甚至几十万条事件,数据库根本扛不住。这时候就需要消息队列来做缓冲和解耦。

Kafka 是目前实时数据 pipeline 的事实标准。我简单讲几个核心概念,够你上手用。

2.3.1 Topic 与 Partition

Topic 是消息的分类。比如你建一个 topic 叫 user_behavior,所有用户行为事件都往这个 topic 里发。Partition 是 topic 的分片,每个 partition 里的消息是有序的。

为什么要分 partition?为了并行。一个 topic 有 8 个 partition,就可以被 8 个消费者同时消费,吞吐量直接翻 8 倍。

# 创建 topic 示例
bin/kafka-topics.sh --create \
  --topic user_behavior \
  --partitions 8 \
  --replication-factor 3 \
  --bootstrap-server localhost:9092
我的建议:partition 数量不是越多越好。每个 partition 对应一个文件句柄,太多会浪费资源。一般按「目标峰值吞吐量 / 单个 consumer 处理能力」来算。比如你预估峰值 10 万条/秒,单个 consumer 能处理 2 万条/秒,那就设 5-8 个 partition。

2.3.2 Producer 与 Consumer

Producer 是生产者,负责把消息发到 Kafka。Consumer 是消费者,负责从 Kafka 拉取消息处理。两者通过 topic 解耦——producer 不需要知道谁在消费,consumer 也不需要知道谁在生产。

这里有个关键点:消息的可靠性。Producer 发送消息时可以设置 ack 级别:

ack 级别 含义 可靠性 性能
0 不等待确认,直接发下一条 最低,可能丢消息 最高
1 等待 leader 确认 中等,leader 挂了可能丢 中等
all 等待所有副本确认 最高,不丢消息 最低
我曾经犯过的错:早期为了追求吞吐量,把 ack 设成了 0。结果有一次 Kafka 集群抖动,丢了 3 分钟的用户行为数据,导致那天的转化率报表完全不准。从那以后,用户行为数据我全部用 ack=all,宁可慢一点,不能丢数据。

2.3.3 数据采集的整体架构

把前端埋点、服务端日志和 Kafka 串起来,就是一套完整的数据采集 pipeline。我画了一张图帮你理解:

用户行为数据采集架构 前端埋点 代码埋点 / 全埋点 浏览器 / App 事件 服务端日志 业务日志采集 Nginx / 应用日志 Kafka 集群 Topic: user_behavior Partition: 8 | Replication: 3 实时计算 Flink / Spark Streaming 实时聚合、规则匹配 离线存储 HDFS / ClickHouse 批量分析、报表 前端埋点与服务端日志通过 event_id 关联,统一进入 Kafka,再分发给实时和离线两条链路

你看,前端埋点和服务端日志是两条独立的采集通道,但它们最终都汇入 Kafka。Kafka 在这里扮演了「数据总线」的角色——上游生产者只管往里写,下游消费者只管往外读,互不干扰。

2.4 本章小结

数据采集层看起来简单,但细节决定成败。我总结几个要点:

  • 前端埋点:核心路径用代码埋点,探索期用全埋点,两者互补
  • 服务端日志:作为前端埋点的兜底,保证数据不丢
  • Kafka:用 topic 做分类,用 partition 做并行,用 ack 级别做可靠性权衡
  • 整体架构:前端 + 后端双通道采集,统一入 Kafka,再分发给实时和离线链路