3. 埋点采集SDK:架构设计与核心策略
埋点采集SDK,说白了就是用户行为数据的“快递员”。它得把App里发生的各种事件,安全、高效、不丢包地送到服务器。我做过好几个SDK,踩过不少坑,今天把核心设计思路掰开揉碎讲给你听。
核心原则:不影响主线程性能、不丢失数据、不浪费流量、不被恶意篡改。
3.1 SDK整体架构设计
先看一张我常用的分层架构图。它把SDK拆成四层,每层各司其职。
每一层都有明确的边界。我见过不少团队把处理逻辑和网络逻辑混在一起,结果改一个上报策略,埋点采集也跟着崩。分层之后,各层可以独立演进。
3.2 数据缓存策略
缓存是SDK的命脉。网络随时可能断,App随时可能被杀。数据一旦丢了,用户行为分析就成了空中楼阁。
3.2.1 三级缓存模型
我个人习惯用三级缓存:
| 缓存级别 | 存储介质 | 容量上限 | 生命周期 | 用途 |
|---|---|---|---|---|
| L1 | 内存队列 | 200条 | App存活期间 | 快速写入,减少IO |
| L2 | SQLite数据库 | 5000条 | 7天 | 持久化,防丢失 |
| L3 | 文件系统 | 100MB | 30天 | 兜底,极端情况 |
L1内存队列:事件来了先塞进内存队列。队列满了就批量刷到L2。这样主线程只需要O(1)的入队操作,几乎无开销。
L2数据库:我用SQLite,但有个坑——事务要分批。我曾经一次性插入3000条数据,直接卡了主线程200毫秒。后来改成每500条一个事务,毫秒级完成。
L3文件兜底:数据库写不进去了怎么办?比如磁盘空间不足。这时候序列化成JSON文件,存到缓存目录。等空间恢复了再回写到数据库。
注意:文件缓存一定要限制总大小。我见过一个SDK没做限制,用户手机里攒了2GB的埋点文件,直接被用户卸载了。
3.2.2 缓存淘汰策略
缓存满了怎么办?不能无限制存下去。我常用的策略:
- 按时间淘汰:超过7天的数据直接丢弃。用户行为分析不需要考古。
- 按优先级淘汰:关键事件(如支付、登录)优先保留,普通浏览事件可以丢弃。
- 按大小淘汰:总缓存超过阈值时,删除最早的非关键事件。
我的经验:给每个事件加一个 priority 字段。上报时高优先级先发,缓存满时低优先级先删。这个字段成本极低,但关键时刻能救命。
3.3 网络上报策略
上报策略直接影响服务器压力和用户流量。我见过最粗暴的做法——来一条发一条。结果用户在地铁里刷App,SDK发了200次请求,200次都失败,电池和流量全浪费了。
3.3.1 批量上报
把多条事件打包成一个请求。我常用的参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 批量大小 | 50条/批 | 太少浪费连接,太多增加失败重试成本 |
| 上报间隔 | 10秒 | 兼顾实时性和批量效率 |
| 最大延迟 | 60秒 | 超过这个时间,即使不够50条也要发 |
3.3.2 网络状态感知
SDK应该感知网络状态:
- WiFi下:正常上报,批量大小可以放大到100条。
- 移动网络下:降低频率,批量大小缩到30条,避免消耗用户流量。
- 弱网下:只上报关键事件,普通事件缓存等待。
- 无网络:全部缓存,等网络恢复后一次性补发。
避坑指南:我曾经在弱网检测上犯过傻——每次请求前都去ping一下服务器。结果用户没崩溃,SDK自己先把自己ping崩溃了。后来改用系统提供的 NetworkCallback,监听网络变化,零额外开销。
3.3.3 重试与退避
上报失败是常态。我的重试策略:
// 指数退避 + 随机抖动
retryDelay = min(initialDelay * 2^retryCount, maxDelay)
retryDelay += random(0, 1000) // 避免惊群效应
// 参数示例
initialDelay = 1秒
maxDelay = 60秒
maxRetries = 5
重试超过5次还失败?别死磕了。把数据标记为“待重试”,等下一次批量上报周期再试。一直重试只会浪费电。
3.4 数据压缩与加密
数据在网络上裸奔,我是不放心的。压缩能省流量,加密能保安全。
3.4.1 压缩策略
JSON文本的压缩率很高。我常用的方案:
| 压缩算法 | 压缩率 | CPU开销 | 适用场景 |
|---|---|---|---|
| GZIP | 70%-80% | 中等 | 通用场景,服务端广泛支持 |
| ZSTD | 75%-85% | 较低 | 移动端,压缩更快 |
| LZ4 | 50%-60% | 极低 | 实时性要求高的场景 |
我个人倾向用 GZIP。虽然压缩率不是最高的,但服务端不需要额外装库,兼容性最好。如果你们团队能统一技术栈,ZSTD是更好的选择。
小技巧:压缩前先去掉JSON里的空格和换行。别小看这一步,能多压5%-10%。
3.4.2 加密方案
用户行为数据涉及隐私,必须加密。我的方案:
- 传输层:HTTPS是底线。别用HTTP,哪怕只是内网测试。
- 内容层:AES-256-GCM加密payload。密钥由服务端下发,定期轮换。
- 签名层:每个请求带HMAC-SHA256签名,防止数据被篡改。
// 加密流程示例
1. 生成随机IV(12字节)
2. 用AES-256-GCM加密payload
3. 拼接:IV + 密文 + GCM Tag
4. 对整个包做HMAC-SHA256签名
5. 发送:签名 + 加密包
重要:密钥不要硬编码在客户端代码里。我见过有人把密钥写在Java代码的字符串里,反编译一下就全暴露了。正确的做法是:启动时从服务端获取密钥,存在Keychain或KeyStore里。
3.5 整体流程串联
把上面几块串起来,一个完整的上报流程是这样的:
- 业务层调用
track(event),事件进入L1内存队列。 - 处理层清洗数据,补充设备ID、时间戳、用户ID等上下文。
- 每10秒或队列满50条,数据刷入L2数据库。
- 上报线程从数据库取出待发送数据,批量打包。
- 压缩(GZIP),加密(AES-256-GCM),签名(HMAC)。
- 检查网络状态,选择合适的上报策略。
- 发送请求。成功则删除已发送数据;失败则标记重试。
- 重试超过5次?数据保留,等下次周期再试。
这套架构我用了好几年,支撑过日活千万级的App。核心就一句话:稳、快、省、安全。你照着这个思路去设计,基本不会出大问题。