4. 状态机核心定义:待上报、上报中、上报成功、上报失败、重试中状态枚举
埋点数据从产生到最终送达服务端,经历一系列不可逆或可回溯的状态迁移。本章精确定义五种核心状态及其转移规则,是后续缓存策略、上报线程调度、失败重试、持久化机制的模型基础。
4.1 状态枚举定义
每个埋点事件(Event)在内存/磁盘中至少包含一个 status 字段,取值为以下枚举之一:
| 状态名 | 枚举值 | 语义 | 所属场景 |
|---|---|---|---|
| PENDING | 0 | 待上报:事件已创建,尚未进入上报通道 | 刚构造 / 从磁盘恢复后 |
| UPLOADING | 1 | 上报中:事件已出队,正在等待或发送 HTTP 请求 | 上报线程取出后 |
| SUCCESS | 2 | 上报成功:服务端返回 2xx,确认持久化 | 收到正常响应后 |
| FAILED | 3 | 上报失败:服务端返回非 2xx / 网络错误 | 请求异常后 |
| RETRYING | 4 | 重试中:失败后进入指数退避等待队列 | 重试调度器接管 |
设计决策: 不将“SUCCESS → 删除”作为状态,而是由回收器直接移除 SUCCESS 事件,保持状态机干净。
重试中的事件仍具备“重试次数”、“下次尝试时间戳”等扩展字段,但核心状态只用一个枚举。
4.2 状态转移图(核心 SVG)
4.3 代码枚举实现(TypeScript / C++ 双版本)
客户端 SDK 中枚举类型定义示例:
// TypeScript 版本
export enum EventStatus {
PENDING = 0,
UPLOADING = 1,
SUCCESS = 2,
FAILED = 3,
RETRYING = 4,
}
// 状态转移校验函数
export function canTransition(from: EventStatus, to: EventStatus): boolean {
const allowed: Record<EventStatus, EventStatus[]> = {
[EventStatus.PENDING]: [EventStatus.UPLOADING],
[EventStatus.UPLOADING]: [EventStatus.SUCCESS, EventStatus.FAILED],
[EventStatus.SUCCESS]: [], // 应由回收器移除
[EventStatus.FAILED]: [EventStatus.RETRYING],
[EventStatus.RETRYING]: [EventStatus.UPLOADING],
}
return allowed[from]?.includes(to) ?? false
}
// C++ 版本
enum class EventStatus : uint8_t {
PENDING = 0,
UPLOADING = 1,
SUCCESS = 2,
FAILED = 3,
RETRYING = 4,
};
bool CanTransition(EventStatus from, EventStatus to) {
switch (from) {
case EventStatus::PENDING: return to == EventStatus::UPLOADING;
case EventStatus::UPLOADING: return to == EventStatus::SUCCESS || to == EventStatus::FAILED;
case EventStatus::FAILED: return to == EventStatus::RETRYING;
case EventStatus::RETRYING: return to == EventStatus::UPLOADING;
default: return false;
}
}
4.4 状态持久化与恢复
事件序列化时,status 字段必须写入磁盘缓存(SQLite / JSON / Protobuf)。应用重启后,所有事件从磁盘加载并恢复为 PENDING 状态,RETRYING 状态需特殊处理:重试计数和下次尝试时间戳应一并恢复,若已过期则直接转为 UPLOADING。
最佳实践: 恢复时遍历所有 RETRYING 事件,计算当前时间与下次尝试时间戳。若差值 > 30s(可配置),则视作超时回退到 UPLOADING,避免大量事件积压导致假死。
// 伪代码:恢复逻辑
for each event in disk_cache:
if event.status == RETRYING && now >= event.next_retry_at:
event.status = UPLOADING
else:
event.status = PENDING // 统一落回待上报
add_to_memory_queue(event)
4.5 常见错误与调试
| 错误场景 | 表现 | 原因 | 修复手段 |
|---|---|---|---|
| SUCCESS 事件被重复上报 | 服务端对数重复 | 状态未持久化即关闭应用 | 成功回调后必须同步写入缓存 |
| FAILED 直接退回 PENDING | 无限重试无退避 | 跳过 RETRYING 状态 | 强制校验转移规则 |
| Retrying 事件不触发上报 | 队列堆积 | 重试时间戳未正确比较 | 检查定时器精度 |
| 并发上报导致状态冲突 | 日志打印非法转移 | 多线程未加锁 | 使用 CAS 或原子操作 |
4.6 稳定性与性能优化
- 原子状态切换: 使用
std::atomic<EventStatus>或 CAS(Compare-And-Swap)避免多线程同时修改状态导致脏读。 - 状态校验插桩: 调试版本启用
canTransition()断言,提前捕获非法转移。 - 状态压缩: 嵌入式环境中可使用嵌套位域(2 bits 存储 5 种状态)节省内存,但枚举值需与序列化对齐。
- 监控指标: 暴露各状态计数(statsd / prometheus),观察 FAILED 与 RETRYING 比例,辅助调整重试策略。
注意: 切勿将 RETRYING 解释为“待重试”而允许回调为 PENDING,否则重试次数字段将失去意义,触发无限制重试风暴。