异步任务与可靠性
为什么采用异步处理
对象存储中的上传、物理删除、回源、审核和备份可能持续数秒到数小时。Cloud Blox 将“请求已可靠接收”和“最终副作用已完成”分开:
- 请求阶段尽快完成校验和持久化;
- 耗时工作由后台消费者执行;
- 失败任务保留状态并重试;
- 运维人员可以查看、取消或重新执行任务;
- 多区域部署可以把任务路由到数据所在区域。
七类业务队列
当前业务队列包括:
- 上传:把临时文件写入最终存储通道;
- 审核:执行内容审核,通过后继续上传;
- 删除:递减引用并在满足条件时删除物理文件;
- 副本:把文件复制到冗余通道;
- 回源:从外部源站拉取内容并进入存储流程;
- 备份:把对象复制到存储桶备份目标;
- 恢复:从备份目标恢复主通道缺失内容。
队列数量与能力会随发行版变化。复制和移动属于对象操作,不应与独立业务队列数量混淆。
可靠投递模型
stateDiagram-v2
[*] --> Pending: 业务事务保存任务
Pending --> Dispatching: 认领投递权
Dispatching --> Dispatched: 已进入 Redis 队列
Dispatching --> Pending: 投递失败并安排重试
Dispatched --> Running: Worker 取得执行租约
Running --> Completed: 副作用和状态均完成
Running --> Pending: 可重试失败
Running --> Failed: 不可重试或超过上限
Running --> Cancelled: 用户取消
Running --> Pending: 执行租约过期后恢复
核心原则是:
- MySQL 中的任务记录代表可靠业务意图;
- Redis 队列负责及时唤醒消费者;
- 即时投递失败不会删除业务意图;
- 定时扫描会补投到期或卡住的任务;
- 重复投递通过稳定任务标识和状态认领消解;
- Worker 必须先取得执行所有权,再产生副作用。
执行租约与故障恢复
任务开始执行后会持有带所有者的执行租约,并周期续租:
- 正常完成后写入终态;
- 进程崩溃或节点失联后,租约到期;
- 其他消费者可以恢复并重新认领;
- 续租失败时应终止当前执行,避免两个节点继续产生副作用;
- 任务结果写入失败时会重试或使用降级更新,避免长期停留在“执行中”。
对于上传和回源,重试还会考虑多个通道:当前通道失败后可以切换其他候选通道,直到所有通道达到各自重试上限。
幂等边界
任务投递
相同持久任务重复投递不会创建第二个业务任务。手动重试会生成新的队列投递标识,但仍关联原任务记录。
上传
- 对象和上传任务意图一起保存;
- 相同内容支持秒传;
- 覆盖同名对象会取消被替代的待执行上传;
- 客户端分片上传使用稳定操作标识,可恢复已完成分片和最终结果。
删除
- 同一删除任务重试不会重复递减引用;
- 物理文件删除前需要取得删除所有权;
- 删除完成、重试和失败分别持久化;
- 只有引用归零且删除条件满足时才删除物理文件。
计费
- 每个计费事件有唯一标识;
- 分析存储写入成功但确认失败时允许重放;
- 日结算按用户和账期防重复;
- 账单、扣费和钱包流水在同一事务中完成。
区域队列
区域相关任务会进入带区域标识的队列。节点只消费自己负责的区域,同时保留基础队列作为无区域任务和兼容场景的兜底。
运维需要确保:
- 存储桶区域配置与节点端点一致;
- 每个活跃区域至少有健康消费者;
- 基础队列没有持续积压;
- 区域下线前迁移或清空其待执行任务;
- 节点标签、区域名称和队列名称在监控中可对应。
计划任务
当前版本最多包含十五项内置计划任务,部分任务受发行版和配置开关控制。
业务与计费
- 小时用量快照;
- 日结算;
- 计费事件投递;
- 资源包过期。
存储与对象
- 统计刷新;
- 临时文件清理;
- 对象到期删除;
- 副本备份;
- 数据迁移;
- 待执行任务补投。
运维与数据治理
- 区域健康检查;
- 节点指标采集;
- ClickHouse 日志保留策略同步;
- MySQL 日志清理;
- MySQL 与 ClickHouse 数据备份。
分布式调度
全局任务使用 Redis 租约避免多节点重复执行。关键财务和状态任务在无法取得锁时应停止执行,统计类任务可以根据风险选择降级策略。区域任务由负责该区域的节点执行。
多节点部署必须避免把“主节点标识”当作唯一防重手段。运维验收应模拟:
- 两个节点同时触发同一任务;
- Redis 短暂不可用;
- 持锁节点执行中崩溃;
- 任务执行超过初始锁时长;
- 手动执行与自动调度重叠。
关闭顺序
应用退出时按以下顺序收敛:
- 停止接收新 HTTP 流量;
- 取消应用生命周期;
- 等待计划任务和后台任务退出;
- 等待队列消费者停止;
- 关闭缓存、队列和 Redis 连接;
- 排空日志并关闭 ClickHouse 和 MySQL。
每个阶段都需要超时上限,防止外部存储或队列异常使进程永久无法退出。
运维观察重点
- 各队列等待、执行、重试和失败数量;
- 最老待执行任务年龄;
- 任务投递重试和执行租约回收次数;
- Worker 数量与当前并发;
- 各区域是否有消费者;
- 上传临时目录使用率;
- 计费事件积压与最老事件年龄;
- 小时快照完整率和结算水位;
- 计划任务最近成功时间、持续时间和连续失败次数。
已知运行边界
- Redis 是异步上传和可靠队列的关键依赖,不能只按“可选缓存”对待。
- 默认单应用实例使用本地临时文件;水平扩容前需要处理会话持久化、共享临时存储或请求粘性。
- 浏览器断点续传在应用重启和节点切换场景需要额外的持久化保障。
- 长时间外部存储操作必须同时受任务超时、网络超时和关闭信号约束。