4. 特征存储故障:Redis/MySQL/对象存储等后端存储的常见故障与数据一致性校验

特征平台跑起来之后,最怕什么?我个人最怕的是存储层出问题。你想想看,特征数据全在背后撑着,一旦存储挂了,线上推理直接断粮。今天我们就聊聊三种主流后端存储——Redis、MySQL、对象存储——的常见故障模式,以及怎么保证数据一致性。

4.1 Redis 特征缓存:丢了数据怎么办?

Redis 在特征平台里通常扮演热缓存角色。特征值从离线算好,灌进 Redis,线上服务毫秒级读取。但 Redis 本身是内存型数据库,故障场景很典型。

4.1.1 常见故障一:缓存雪崩与击穿

缓存雪崩说白了就是大量特征 key 在同一时间过期,请求全部打到下游 MySQL 或对象存储。我在项目中遇到过,双十一大促前特征刷新任务没做散列,所有 key 的 TTL 设成了同一个时间点。结果零点一过,Redis 瞬间空了一半,后端存储被打到 OOM。

避坑指南:

  • TTL 加随机偏移量,比如基础值 3600s,再随机 ±300s
  • 热点特征用本地缓存兜底(Caffeine 或 Guava Cache)
  • 开启 Redis 的 lazy-free 和异步淘汰

4.1.2 常见故障二:主从切换导致数据丢失

Redis 主从架构下,如果主节点宕机,从节点晋升为主。但异步复制可能导致部分特征数据没同步过去。我记得有一次线上特征回刷,主节点写入一批新特征值,还没来得及同步就挂了。切换后,线上读取到的全是旧值,模型推理结果直接偏了。

注意:Redis 默认是异步复制。如果特征数据对一致性要求高,建议开启 WAIT 命令或使用 Redisson 的写后读校验。但 WAIT 会降低写入性能,需要权衡。

4.1.3 常见故障三:内存打满与 OOM

特征量增长快,Redis 内存配置没跟上,就会触发 OOM killer。我见过一个案例,特征平台每天新增 2000 个特征,但 Redis maxmemory 一直没调。三个月后某个凌晨,Redis 进程直接被系统杀掉。

解决方案:

  • 设置 maxmemory-policy allkeys-lru,避免 OOM
  • 监控 used_memory / maxmemory 比例,超过 80% 告警
  • 定期清理过期特征,别让僵尸 key 占着内存

4.2 MySQL 特征存储:关系型数据库的坑

MySQL 在特征平台里通常存特征元数据、特征版本、以及部分离线特征宽表。它的问题往往出在写入压力和事务冲突上。

4.2.1 常见故障一:写入瓶颈与死锁

特征平台每天有大量特征写入任务,如果多个任务同时更新同一张特征表,很容易出现死锁。我遇到过最夸张的一次,8 个特征刷新任务同时 UPDATE 同一行记录,MySQL 直接报 Deadlock found。结果那批特征全部写入失败,线上推理用了旧数据。

避坑指南:

  • 特征表设计时,按特征 ID 做分片,避免热点行
  • 写入任务加随机延迟,错开执行时间
  • 使用 INSERT ... ON DUPLICATE KEY UPDATE 代替先查后改

4.2.2 常见故障二:主从延迟导致特征不一致

特征平台通常读写分离:写入主库,读取从库。但主从延迟会导致刚写入的特征读不到。我记得有一次特征上线,写入主库后立即查询从库,结果返回空。线上服务直接降级,用了默认值。

我的习惯:对一致性敏感的特征,强制走主库读取。或者在写入后 sleep 500ms 再切到从库。虽然不优雅,但有效。

4.2.3 常见故障三:大事务与锁等待

特征批量导入时,如果单事务涉及的行数太多,会长时间持有行锁,阻塞其他写入。我见过一个特征导入任务,一次 INSERT 10 万行,事务执行了 30 秒,导致其他特征写入全部超时。

解决方案:

  • 批量写入拆成小批次,每批 1000-5000 行
  • 使用 LOAD DATA LOCAL INFILE 代替逐行 INSERT
  • 监控 innodb_row_lock_current_waits,超过阈值自动熔断

4.3 对象存储:特征文件丢了怎么办?

对象存储(S3、OSS、COS)在特征平台里存离线特征快照、模型特征映射文件、以及历史特征数据。它的故障模式跟 Redis/MySQL 完全不同。

4.3.1 常见故障一:文件损坏与校验失败

特征文件上传过程中网络闪断,可能导致文件不完整。我遇到过特征快照上传到 OSS 后,ETag 跟本地 MD5 对不上。线上加载时直接报错,特征服务重启失败。

避坑指南:

  • 上传完成后,主动调用 HeadObject 校验 Content-MD5
  • 使用分片上传,每个分片独立校验
  • 特征文件加版本号,回滚时能切到上一版本

4.3.2 常见故障二:访问权限与限流

对象存储的访问策略配置错误,会导致特征读取失败。我见过一个案例,特征平台迁移到新 Bucket,但 RAM 角色没更新,线上服务一直报 AccessDenied。排查了半小时才发现是权限问题。

核心原则:对象存储的故障排查,先看 HTTP 状态码。403 是权限问题,429 是限流,503 是服务端异常。别一上来就查代码。

4.3.3 常见故障三:跨区域复制延迟

如果特征平台跨地域部署,对象存储的跨区域复制可能有分钟级延迟。我记得有一次,北京地域写入特征快照,上海地域读取时还是旧文件。线上推理用了过期的特征数据。

解决方案:

  • 对时效性敏感的特征,使用同地域读写
  • 跨地域场景,写入后主动等待复制完成(ListObjectVersions 校验)
  • 使用对象存储的事件通知,复制完成后触发回调

4.4 数据一致性校验:怎么保证特征不脏?

三种存储都可能出现数据不一致。我总结了一套校验方法,核心是「写入时校验 + 定时对账 + 异常自动修复」。

4.4.1 写入时校验

每次特征写入后,立即回读校验。比如写入 Redis 后,用 GET 命令读回来比对值。写入 MySQL 后,SELECT 出来校验。写入对象存储后,下载文件比对 MD5。

// 伪代码:Redis 写入后校验
String key = "feature:user:1001:age";
String value = "28";
jedis.set(key, value);
String readBack = jedis.get(key);
if (!value.equals(readBack)) {
    // 告警并重试
    alert("特征写入不一致: " + key);
    retryWrite(key, value);
}

4.4.2 定时对账

每天凌晨跑对账任务,对比不同存储之间的特征数据。比如 Redis 里的热特征 vs MySQL 里的元数据,或者对象存储里的特征快照 vs MySQL 里的版本记录。

对账维度 源存储 目标存储 校验方式
特征值一致性 Redis MySQL 逐 key 比对值
特征版本一致性 对象存储 MySQL 比对版本号 + 文件 MD5
特征数量一致性 Redis MySQL 比对 key 总数

4.4.3 异常自动修复

发现不一致后,自动触发修复流程。比如 Redis 里特征值跟 MySQL 不一致,就从 MySQL 拉取正确值覆盖 Redis。修复完成后,再校验一次,确保修复成功。

我的经验:修复流程一定要加「熔断」机制。如果连续修复失败超过 3 次,就停止自动修复,转人工处理。否则可能越修越乱。

4.5 知识体系总览

下面这张图总结了特征存储故障的核心链路和校验方法。你可以把它当作排查时的思维导图。

特征存储故障排查知识体系 Redis 特征缓存 MySQL 特征存储 对象存储特征文件 Redis 常见故障 缓存雪崩/击穿 主从切换丢数据 MySQL 常见故障 写入瓶颈/死锁 主从延迟/大事务 对象存储常见故障 文件损坏/权限问题 跨区域复制延迟 数据一致性校验 写入时校验 写入后立即回读 定时对账 跨存储比对特征 异常自动修复 熔断 + 重试 核心原则:写入时校验 + 定时对账 + 异常自动修复,三层兜底

4.6 小结

特征存储故障排查,说白了就是三件事:知道每种存储的典型故障模式,掌握数据一致性校验方法,建立自动修复机制。我个人建议,先把 Redis 的缓存雪崩和 MySQL 的死锁搞定,这两个是线上最常遇到的。对象存储的故障相对少,但一出就是大事,文件损坏或权限问题可能导致整个特征服务不可用。

嗯,这一章的内容就到这里。记住,存储层是特征平台的根基,根基不稳,上面再好的模型也是白搭。