共计 2736 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点分析
随着 ChatGPT API 的使用成本增加,拼单模式成为开发者降低成本的常见方案。但这种模式面临几个核心挑战:

- 配额限制问题:OpenAI 对每个账户有严格的每分钟 / 每天调用次数限制,多人共享时容易触发限流
- 并发竞争:多个用户同时发起请求时,可能出现超额调用导致服务中断
- 成本分摊:不同用户调用次数差异大时,如何公平分摊费用成为难题
架构设计选型
方案对比
- 反向代理(Nginx)
- 优点:配置简单,性能损耗低
-
缺点:缺乏业务逻辑处理能力,无法实现精细化的配额管理
-
API 网关(Kong)
- 优点:支持插件扩展,具备基础限流能力
-
缺点:复杂业务规则仍需后端服务实现
-
服务网格(Istio)
- 优点:基础设施层流量管控
- 缺点:学习成本高,对业务逻辑支持弱
最终选择 自定义微服务网关 方案,平衡灵活性与控制力。
分层架构
flowchart TD
A[客户端] --> B[API 网关]
B --> C{限流检查}
C -->| 通过 | D[业务服务]
D --> E[额度管理]
D --> F[ChatGPT API]
E --> G[(Redis)]
F --> H[(MySQL)]
核心实现细节
JWT 鉴权中间件
func AuthMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {tokenString := r.Header.Get("Authorization")
if tokenString == "" {log.Warn("missing auth token")
w.WriteHeader(http.StatusUnauthorized)
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, fmt.Errorf("unexpected signing method")
}
return []byte(config.SecretKey), nil
})
if err != nil || !token.Valid {log.Error("invalid token", zap.Error(err))
w.WriteHeader(http.StatusForbidden)
return
}
claims := token.Claims.(jwt.MapClaims)
ctx := context.WithValue(r.Context(), "userID", claims["sub"])
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Redis+Lua 限流实现
-- token_bucket.lua
local key = KEYS[1] -- 用户 ID
local limit = tonumber(ARGV[1]) -- 桶容量
local interval = tonumber(ARGV[2]) -- 时间窗口(秒)
local now = tonumber(ARGV[3]) -- 当前时间戳
local requested = tonumber(ARGV[4]) -- 请求令牌数
local last_time = redis.call("hget", key, "last_time") or now
local tokens = redis.call("hget", key, "tokens") or limit
-- 计算新增令牌
local elapsed = now - last_time
local refill = math.floor(elapsed / interval)
local new_tokens = math.min(tokens + refill, limit)
if new_tokens >= requested then
redis.call("hset", key, "last_time", now)
redis.call("hset", key, "tokens", new_tokens - requested)
return 1 -- 成功
else
return 0 -- 失败
end
Saga 事务模式
sequenceDiagram
participant C as 客户端
participant O as 订单服务
participant A as 额度服务
participant G as GPT 服务
C->>O: 创建拼单
O->>A: 预扣额度(预留状态)
A-->>O: 确认预留
O->>G: 调用 API
alt 调用成功
G-->>O: 返回结果
O->>A: 确认扣减
else 调用失败
G-->>O: 返回错误
O->>A: 取消预留
end
生产环境考量
压测数据(单节点)
| 并发数 | P50(ms) | P95(ms) | P99(ms) | 成功率 |
|---|---|---|---|---|
| 100 | 120 | 210 | 350 | 100% |
| 500 | 150 | 380 | 650 | 99.7% |
| 1000 | 230 | 520 | 1100 | 98.2% |
熔断策略配置
circuit_breaker:
failure_threshold: 3 # 连续失败次数
success_threshold: 5 # 恢复所需成功次数
timeout_ms: 5000 # 熔断持续时间
sliding_window_size: 10 # 统计窗口大小
常见问题解决方案
- 幂等性设计
- 为每个请求生成唯一 ID
-
在 Redis 记录处理中的请求
func DedupeMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {requestID := r.Header.Get("X-Request-ID") if requestID == "" {w.WriteHeader(http.StatusBadRequest) return } key := fmt.Sprintf("req:%s", requestID) if redis.SetNX(key, "1", 24*time.Hour).Err() != nil {next.ServeHTTP(w, r) } else {w.WriteHeader(http.StatusConflict) } }) } -
中途退出处理
- 定时任务扫描长时间未完成的预留额度
-
实现 TTL 自动回滚机制
-
API 兼容性
- 抽象接口层隔离不同版本 API
- 配置中心管理端点 URL
延伸思考
如何设计跨 Region 的配额同步方案?可以考虑:
- 基于 etcd 的分布式锁 + 租约机制
- 使用 CRDT 实现最终一致性
- 区域间定时对账补偿
实际项目中,我们还需要持续监控真实流量模式,动态调整限流策略。建议每周分析配额使用情况,优化拼单组成员分配算法。
正文完
发表至: 未分类
近一天内
