64 lines
7.0 KiB
Plaintext
64 lines
7.0 KiB
Plaintext
---
|
||
title: 任务缓存与队列稳定性
|
||
description: 热点回源保护、并发租约、故障边界和上线验收
|
||
---
|
||
|
||
# 任务缓存与队列稳定性
|
||
|
||
## 范围与事实源
|
||
|
||
当前任务仍由数据库持久化、API 进程内 worker 领取和执行,Redis 负责跨实例并发协调。本轮是热点读取和租约的止血修复,不是独立任务系统迁移,也不能替代线上事故取证。
|
||
|
||
- 数据库继续作为任务、额度、账务和权限的事实源。没有把任务或账本迁入 Redis,没有修改表结构或新增迁移。
|
||
- 新增缓存是每个后端实例的有界内存读缓存,不是通用 Redis 业务缓存。实例增加时,缓存容量与不同实例的回源量会随之增加。
|
||
- handler 负责 SSE 协议和登录上下文;service 负责读缓存、并发协调和执行生命周期;repository 负责有条件的租约写入与事务。
|
||
|
||
## 热点读取保护
|
||
|
||
| 读路径 | 缓存 / 回源限制 | 故障行为 |
|
||
| --- | --- | --- |
|
||
| worker / 渠道全局并发策略 | 2 秒 TTL,1 条目、1 个同时回源,正文预算 1 KiB | 读取失败不使用默认高并发;暂停投递或返回明确错误 |
|
||
| 文本任务 SSE 回放 | 750 毫秒 TTL,256 条目、8 个不同 key 同时回源,正文预算 8 MiB | 同 key 合并回源;超过容量返回繁忙,不创建无界的不同 key 等待队列 |
|
||
| Redis 路由版本检查 | 250 毫秒 TTL,1 个同时回源 | 失败保留原有本地版本回退;不保证 Redis 故障时跨实例实时失效 |
|
||
| 模型路由目录 | 沿用 30 秒 TTL,刷新串行,失败冷却 2 秒,数据库回源期限 5 秒 | 首次失败明确返回错误;已有快照只能在同版本、最多 5 分钟的原有过期窗口内降级 |
|
||
|
||
读缓存按 LRU 淘汰,正文预算是估算值,不等于进程总内存上限;回源中的数据、当前请求和等待中的连接不包含在正文预算内。超大结果仅返回给当前读取者,不保留为缓存。普通失败冷却 1 秒,不返回过期成功值;请求取消和超时不缓存。回源异常退出释放容量和等待者,原始 panic 仍交给原有错误处理。
|
||
|
||
并发策略保存或重置成功后立即清除本实例缓存,其他实例在 2 秒 TTL 后重新读取。计费、配额和其他调用 `RuntimePolicy()` 的强校验路径仍直接读取数据库。
|
||
|
||
SSE 缓存 key 包含用户、任务和 `after` 游标,回源仍检查归属;返回副本避免调用者改写其他连接的增量。`GET /api/tasks/:id/text-events` 的事件格式、递增序号、`Last-Event-ID` / `?after=` 和 15 秒心跳不变。终态最多受短缓存窗口延迟;已经建立的流遇到回源失败仍发送 error 并关闭。`/text-deltas` 普通查询没有纳入此缓存。
|
||
|
||
这是合并相同游标的轮询读取,不是事件驱动推送;不同游标和不同实例仍可能分别查询数据库。连接总量、客户端重连退避及任务删除后的缓存失效仍需后续治理。
|
||
|
||
## worker 与渠道槽位
|
||
|
||
- worker 全局槽位由固定 45 分钟 TTL 改为 1 分钟租约,每 15 秒与数据库任务租约一起续期;数据库任务租约仍为 45 秒。
|
||
- 每次领取生成独立 owner,不再让一个进程的所有执行共享 owner。领取数据库操作有 5 秒期限,启动策略读取有 3 秒期限。
|
||
- Redis 协调客户端关闭透明命令重试;槽位领取、续期与释放使用最多 2 秒期限。Redis 获取槽位失败时不降级成本机无限执行。
|
||
- 槽位丢失、过期或续期失败会取消该执行的 context。已经失效的槽位不能通过续期复活;释放幂等且只移除本次 token。释放失败记录日志,由租约过期回收。
|
||
- 共享 Redis 槽位键按集合内最晚的租约保留过期余量,避免短任务或新版本的短租约把旧长任务槽位提前删除。旧版本脚本本身不会获得这一保护,仍建议排空后发布。
|
||
- 渠道满载时从 200 毫秒开始指数退避,上限 2 秒,每轮在半窗口至完整窗口间随机等待,避免固定每秒五次同步争抢。空闲时仍先立即尝试领取,取消能立即结束等待。
|
||
- worker 主进度、终态、结果事务及延迟回查要求匹配 owner 且租约仍有效。过期执行者不能覆盖已被重新领取的任务;条件更新没有命中时,终态协调器不得继续退款或完成会话。
|
||
|
||
**边界**:租约不是外部供应商的幂等保障。上游请求不一定响应取消;提供商状态回调、部分进度和关联资源写入尚未全部传播 owner,不能宣称所有副作用都已 fencing。渠道等待仍发生在领取 worker 槽位之后,也没有新增全局积压上限或公平调度。其他用途的槽位未统一改为续期模型。
|
||
|
||
## 上线与回滚
|
||
|
||
1. 先保存故障时段的 Redis 内存、淘汰数量、连接数、延迟和重启记录,以及数据库慢查询、连接池等待、任务 queued/running 数量与最老等待时长。必要日志必须脱敏,不能导出密钥和完整用户正文。
|
||
2. 先在测试环境验证 Redis 与 PostgreSQL 双实例:长任务超过原租期仍只占一个槽位;停止 worker 后槽位恢复;Redis 断连时暂停领取;旧 owner 写入失败;SSE 断线续传无重复或漏终态。
|
||
3. 采用维护窗口排空旧执行者,再发布新版本。Compose 当前默认优雅退出时间由 `CANVAS_SHUTDOWN_TIMEOUT` 控制,默认 10 分钟,不足以保证最长 60 分钟渲染自然完成;发布前核对活跃任务,不能把进程退出视为任务排空成功。
|
||
4. 保留 `canvas:slots:*` 等现有 Redis key。旧进程的 45 分钟分数可能暂时占槽,应先确认旧执行者已结束并观察租约自然回收,**禁止用 FLUSHDB、批量删槽位或批量重投收费任务来解堵**。
|
||
5. 回滚不需要数据迁移,但旧版本仍有原来的固定租期和缺失写入保护。避免新旧版本混跑,不应把回滚视为稳定性问题已经解除。
|
||
|
||
本次不部署、不清理线上缓存、不修改线上任务或触发真实收费请求。仅凭代码修复和本地测试,不能认定某次线上事故根因已确认或故障已消除。
|
||
|
||
`TestRuntimeCoordinatorRedisLeasePreservesLongestTTL` 在 PATH 中存在 `redis-server` 时自动启动临时 Unix socket Redis,检查长短租约混用、容量、过期拒绝续期及幂等释放;它不读取 `REDIS_URL`,不连接部署环境。缺少二进制时明确跳过,不视为 Redis 集成已通过。
|
||
|
||
## 下一阶段
|
||
|
||
- 可复用业务数据的 Redis 缓存与主动失效,任务状态快照和事件通知,缓存故障时的总量保护。
|
||
- 独立 worker 进程、可靠投递 / outbox、渠道前置排队、积压上限和公平性;按任务类型定义幂等、重试、延迟和补偿。
|
||
- 将执行 owner 传播到所有提供商回调和副作用;明确上游已提交但本地未落库时的查询恢复,不盲目重复生成。
|
||
- 后台手动投递、暂停、恢复和审计;完整的请求 / 任务 / 回调关联日志与脱敏正文留存。
|
||
- 事件驱动 SSE、连接准入和重连退避,替换按连接的数据库轮询。
|