Files
zt-closet-backend/.codebuddy/plans/model-call-retry_5868b00a.md
2026-09-11 01:41:52 +08:00

87 lines
5.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: model-call-retry
overview: 为抠图(Matting)与分类(Classification)两个模型调用增加可配置的自动重试机制,默认最大重试 3 次;配置项分别加到 MattingModel 与 ClassificationModel 配置段下,并在衣物异步处理流程中接入重试逻辑。
todos:
- id: add-retry-config
content: 在 section.go 结构体及 config.yaml、config.example.yaml 中为 MattingModel/ClassificationModel 增加 MaxRetry 配置
status: completed
- id: default-retry-value
content: 在 main.go 读取配置后对两个模型 MaxRetry
status: completed
dependencies:
- add-retry-config
---
## 产品概述
为衣物图片处理流水线中的抠图(Matting)与分类(Classification)两个模型调用增加自动重试能力,降低下游模型服务偶发失败导致的衣物处理失败率。重试次数通过配置文件分别控制,默认最大重试 3 次。
## 核心功能
- 抠图调用失败时自动重试,默认最大重试 3 次(总尝试 4 次)
- 分类调用失败时自动重试,默认最大重试 3 次(总尝试 4 次)
- 重试次数分别配置在 `MattingModel` 与 `ClassificationModel` 两个配置段下,配置缺失或小于等于 0 时兜底为 3
- 全部重试失败后维持现有失败处理:抠图失败回退上传额度并标记失败;分类失败保留已抠图结果(processed_image)并标记失败,便于用户手动补全
- 删除处理中衣物不回退额度的既有防滥用设计保持不变(不做修改)
## 技术栈
- Go 1.27(使用泛型、`log/slog`、`context` + `time.After`)
- 配置:viper(复用现有 `setting.ReadSection` 读取模式)
- 日志:`log/slog`(复用现有 `slog.WarnContext` / `slog.ErrorContext`)
## 实现方案
### 总体策略
复用现有配置读取与兜底模式,在配置结构体、配置文件中增加 `MaxRetry` 字段;在异步处理函数 `processImageAsync` 中新增一个泛型重试辅助函数 `retryCall`,包裹抠图与分类两处调用,实现失败自动重试,失败分支逻辑保持不变。
### 关键设计
1. **配置层(最小改动)**
- `pkg/setting/section.go` 的 `MattingModelSettingS`、`ClassificationModelSettingS` 各新增 `MaxRetry int` 字段。
- `configs/config.yaml`(实际配置)与 `configs/config.example.yaml`(示例配置)的 `MattingModel`、`ClassificationModel` 段各新增 `MaxRetry: 3`。
- `main.go` 在读取 `MattingModel`、`ClassificationModel` 后,对 `MaxRetry <= 0` 兜底为 3,与现有 `MaxSizeMB <= 0` 兜底 20、`Model == ""` 兜底 `hy4` 的模式保持一致。
2. **重试层(泛型辅助函数)**
- 在 `internal/service/clothes.go` 新增 `retryCall[T any]` 泛型函数:循环尝试 `maxRetry+1` 次;每次失败记录 `slog.WarnContext`;重试间隔使用 `select` + `time.After(1s)` + `ctx.Done()`,保证 ctx 到期时提前终止。
- 抠图与分类调用分别用 `retryCall` 包裹,`maxRetry` 分别取 `global.MattingModelSetting.MaxRetry` 与 `global.ClassificationModelSetting.MaxRetry`。
- `fn` 闭包捕获外部 `ctx`,每次调用内部会重建 multipart body,无需额外重置状态。
3. **失败分支保持不变**
- 抠图全部重试失败后仍调用 `markProcessFailed`(标记失败 + 回退上传额度)。
- 分类全部重试失败后仍保留 `processed_image` 并标记 `ImageProcessFailed`(不回退额度,允许用户手动补全)。
### 关键代码结构
```
// retryCall 对 fn 进行最多 maxRetry 次额外重试(总尝试 maxRetry+1 次)。
// 重试间隔 1s;ctx 到期则提前终止并返回 ctx.Err()。
func retryCall[T any](ctx context.Context, maxRetry int, name string, fn func() (T, error)) (T, error)
// 调用示例
processedData, err := retryCall(ctx, global.MattingModelSetting.MaxRetry, "matting", func() ([]byte, error) {
return mattingClient.Matting(ctx, imageData)
})
```
## 实现注意
- **MaxRetry 语义**:失败后的额外重试次数,总尝试次数 = MaxRetry + 1;默认 3 即总尝试 4 次。
- **超时预算**:异步 ctx 总超时为 3 分钟,单次 HTTP 客户端超时为 120s。`retryCall` 通过 `ctx.Done()` 检查避免超时后继续重试;瞬时失败(连接拒绝、偶发非 200)通常快速返回,3 分钟预算足够。
- **并发限流不受影响**:`imageProcessSem` 信号量在 `processImageAsync` 入口已获取,重试发生在同一 goroutine 内,不会放大并发。
- **blast radius 控制**:仅新增配置字段与重试辅助函数,不修改 `DeleteByUUID` 额度回退逻辑;`config.yaml` 中重复的 `UserDefaultQuota` 段为既有问题,本次不处理。
## 目录结构
```
zt-closet-backend/
├── pkg/setting/section.go # [MODIFY] MattingModelSettingS、ClassificationModelSettingS 各新增 MaxRetry int 字段
├── configs/config.yaml # [MODIFY] MattingModel、ClassificationModel 段各新增 MaxRetry: 3
├── configs/config.example.yaml # [MODIFY] MattingModel、ClassificationModel 段各新增 MaxRetry: 3
├── main.go # [MODIFY] 读取 MattingModel、ClassificationModel 后对 MaxRetry<=0 兜底为 3
└── internal/service/clothes.go # [MODIFY] 新增 retryCall 泛型辅助函数,抠图/分类调用接入重试
```