点击追踪

第 10 封邮件 📧

周五下午,我收到本周第 10 封邮件,主题还是一样:

“我的链接到底被点了多少次?哪个渠道效果最好?我的推广预算花得值不值?”

这些问题我已经听了太多次。我看着自己的代码——创建短链接时只是存了个映射关系,重定向就是 302 跳转。完全没有记录任何访问信息!

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

我知道,点击统计不能再拖了。营销用户需要知道他们的推广效果,我需要记录每次点击的详细信息。但我心里也很清楚——不能影响跳转速度

当前架构
第 4 版:异步统计链路
点击统计不能拖慢跳转,事件进入队列后再实时和离线消费。
跳转
短链访问
302 响应
事件
点击事件
消息队列
统计
实时聚合
离线分析
报表
存储
MySQL
分析表
不阻塞跳转的点击追踪链路
redirect_cached
Step 1
1
访问短码
跳转请求进入短链服务
Step 2
2
检查缓存
优先读取 Redis / 本地缓存
Step 3
3
缓存命中
直接拿到长 URL 和状态
Step 4
4
异步统计
点击事件进入队列,不阻塞跳转
Step 5
5
返回 302
低延迟完成跳转

要追踪什么?

我打开笔记本,开始列出需要追踪的数据:

基础信息

  • 时间戳——什么时候点的
  • 短链接——点了哪个链接
  • 目标 URL——最终跳到哪里

用户信息

  • IP 地址——知道大概位置
  • User-Agent——解析出设备、浏览器、操作系统
  • Referer——从哪里来的(搜索、社交媒体、直接访问)
  • Accept-Language——用户语言偏好

渠道信息

  • UTM 参数——营销人员最关心的来源标记
    • utm_source:来源(微信、微博、抖音)
    • utm_medium:媒介(链接、二维码、Banner)
    • utm_campaign:活动名称

地理位置

  • 国家——IP 推算
  • 城市——IP 推算

我写了数据收集函数:

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

解析 User-Agent

User-Agent 是个复杂的字符串,我写了几个解析函数:

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

第一次尝试:同步写入 🐌

数据收集好了,接下来就是存储。我最开始的想法很简单——直接写数据库:

数据设计要点

  • 核心是在 click_logs 里保存业务事实,而不是把规则散落在应用逻辑里。
  • 关键字段包括 idshort_codelong_urlipcountrycitydevicebrowser,它们决定了后续查询和管理能力。

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

我部署上线,开始监控性能。结果让我倒吸一口凉气:

之前:平均响应时间 20ms
现在:平均响应时间 100ms

跳转速度涨了 5 倍! 😱

我一看数据库监控,每次跳转都要执行一次 INSERT,数据库 QPS 瞬间飙到 1000+。这不行——用户体验会严重下降。

同步写入是死路。我需要异步。

第二次尝试:异步队列 ⚡

我的新方案:缓冲区 + 批量写入 + 后台线程

核心思路:

  1. 收集到点击数据后,不直接写数据库
  2. 放入内存缓冲区
  3. 后台线程定期批量写入数据库
  4. 跳转响应不等待写入完成

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

再次部署上线,监控结果:

平均响应时间:22ms ✅
数据库 QPS:从 1000+ 降到 50(批量写入)

完美!跳转速度恢复正常,数据库压力也大幅降低。

第三次尝试:Kafka 高吞吐 🚀

随着用户量增长,日点击量从 10 万涨到 100 万,再到 1000 万。内存缓冲区方案开始吃力——缓冲区太大占用内存,进程重启会丢失数据。

我引入了 Kafka:

用户点击

[短链接服务] → [Kafka] → [消费者] → [数据库/ClickHouse]

              消息队列缓冲
              峰值可抗 100K+/秒

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

三种方案对比

方案响应延迟吞吐量可靠性复杂度
同步写 DB10-50ms1K/s
异步批量写<1ms10K/s⭐⭐
Kafka<1ms100K+/s⭐⭐⭐

建议:日点击 < 10 万用异步批量写,> 100 万上 Kafka。

数据模型优化 📊

我重新审视了数据表设计,做了些优化:

数据表设计

数据设计要点

  • 核心是在 click_logs 里保存业务事实,而不是把规则散落在应用逻辑里。
  • 关键字段包括 idshort_codeipcity_idbrowserosrefererutm_source,它们决定了后续查询和管理能力。

数据量估算

每条记录约 200 字节

日访问量 50 万 = 每天 100MB = 每月 3GB
日访问量 1000 万 = 每天 2GB = 每月 60GB

查询统计

数据存好了,用户要能查。我写了几个常用查询:

数据设计要点

  • 查询目标是用短码快速找到目标链接、状态或统计结果,避免在跳转路径上做大范围扫描。

统计 API

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

隐私考量 ⚖️

数据收集到什么程度?这是个问题。

我思考了很久,决定这条线:

我会收集

  • ✅ IP(脱敏到前 3 段)
  • ✅ 城市(不收集精确位置)
  • ✅ 设备、浏览器、操作系统
  • ✅ 来源渠道(UTM 参数)

我不会收集

  • ❌ 完整 IP
  • ❌ GPS 位置
  • ❌ 个人身份信息
  • ❌ Cookie 跟踪

IP 脱敏实现

落地思路

  • 这里省略具体语法,只保留设计层面的职责边界。
  • 读这段时重点看:输入是什么、系统做哪些判断、状态如何变化、失败时如何兜底。

这样既能提供有价值的统计数据,又保护了用户隐私。这是我的底线。

想一想

  1. 点击追踪会影响重定向的速度吗?
    会,如果同步写入的话。我改成异步队列后,响应时间从 100ms 降回 20ms。

  2. 如果追踪系统暂时不可用(如 Kafka 挂了),应该怎么处理?
    我的选择是放行——宁可丢数据,也不能影响用户体验。可以在本地文件缓存,等恢复后再补传。

  3. 如何防止伪造点击数据(刷量)?
    这是个难题。我做了几个防护:

    • 限制同一 IP 的短时间点击频率
    • 检测异常 User-Agent
    • 识别代理 IP
    • 统计异常模式(比如连续点击同一链接)