1. 训练进度应该存在哪里
训练页面每隔几秒刷新一次进度。如果每次都查询任务、执行记录、最近指标和日志位置,数据库会承担大量重复读取。把进度放进 Redis 看起来很自然。
真正需要先回答的问题是:Redis 丢失以后,平台还能不能知道任务最终发生了什么?
可以把状态分成四类:
| 信息 | 更合适的来源 |
|---|---|
| 任务由谁创建、用了哪版数据、最终结果 | MySQL |
| Job、Pod、节点和容器当前状态 | Kubernetes API |
| 当前 step、最近 loss、心跳 | Redis 或时序系统 |
| 完整日志、检查点和模型文件 | 对象存储或共享文件系统 |
Redis 很适合保存可重建、短生命周期和高频读取的数据。最终业务事实仍要回写到持久记录。
2. 一个进度键需要表达身份和时效
说明示例:
training:job:1042:progress
status RUNNING
attempt 2
step 7860
total_steps 20000
updated_at 2026-08-07T10:18:31Zattempt 很重要。任务第一次执行失败后,第二个 Worker 不能被第一次延迟到达的心跳覆盖。
写入时可以同时比较执行编号,读取时还要检查 updated_at。很久没有更新的进度应该显示“心跳过期”,不能继续显示成正在运行。
TTL 只控制键何时过期,不负责把 Redis 和 MySQL 自动同步。下面的 120 秒只是示例参数:
EXPIRE training:job:1042:progress 120实际时间要结合上报周期、网络抖动和产品允许的延迟确定。
3. Dashboard 缓存属于派生数据
项目首页可能展示:
数据集版本数量
排队中的训练任务
运行中的推理版本
最近失败任务这些值可以从 MySQL 和 Kubernetes 重新计算,适合缓存。缓存键需要包含项目或租户范围,受到权限影响的统计还要避免跨用户复用。
常见读取流程是:
Request
→ Redis hit → return
→ Redis miss → query source → rebuild cache → return写入业务数据后,可以在事务提交成功后删除相关缓存。删除失败、并发回填旧值和大面积同时过期仍需单独处理。TTL 只能限制陈旧值存在的最长时间之一,不能提供强一致保证。
4. 限流、锁和队列不能混成一个概念
Redis 还常被用于限流、幂等令牌和分布式锁。这些用途需要各自的协议:
- 限流需要明确作用域、时间窗口和超限行为。
- 幂等键需要绑定调用者、请求参数摘要和业务结果。
- 锁需要唯一持有者标识、超时和安全释放。
- 消息队列需要消费确认、重试和积压治理。
一个 SETNX 加过期时间无法自动覆盖所有异常。业务需要先确定“同时只能有一个训练任务”的范围,是项目、数据集版本、GPU 资源还是同一请求。
数据库唯一约束适合守住最终业务不变量。Redis 可以减少争用或快速拒绝,但不应让核心正确性只依赖一个可能过期的键。
5. Redis 不可用时平台怎样退化
需要分别判断功能:
Dashboard 缓存失败 → 有限回源或暂时隐藏统计
训练进度读取失败 → 显示最终状态和“实时进度不可用”
幂等检查失败 → 根据业务风险拒绝或依赖数据库约束
限流组件失败 → 选择 fail-open 或 fail-closed所有请求同时回源 MySQL,可能把缓存故障扩大成数据库故障。回源需要并发上限、超时和熔断策略。
Redis 的 RDB 和 AOF 解决重启后的数据恢复窗口,不会让数据库写入与缓存更新变成一个原子事务。即使启用持久化,可重建缓存与不可丢业务记录仍然应该分开。
6. Java 控制面怎样使用这些状态
Spring Boot 可以把 Redis 包在明确的接口后面:
public record TrainingProgress(
long jobId,
int attempt,
long step,
long totalSteps,
Instant updatedAt
) {}读取服务先验证任务和项目权限,再访问对应进度。Worker 使用受限身份上报自己的任务,不能通过请求参数修改其他任务。
任务完成时,事务内更新 MySQL 的最终状态和产物引用;事务提交后再清理或缩短进度键的保留时间。清理失败可以重试,因为最终结果已经进入持久记录。
Redis 放回 AI 平台后,不只是“让接口更快”的组件,更像是控制面旁边的一层临时状态:让页面及时看到变化,让重复读取减少,同时允许在 Redis 故障后从权威来源恢复。临时状态与持久记录分开放,页面展示和业务操作才能各自准确。