feat: 重试机制
This commit is contained in:
@@ -45,6 +45,7 @@ type KeysConfig struct {
|
||||
FilePath string `json:"filePath"`
|
||||
StartIndex int `json:"startIndex"`
|
||||
BlacklistThreshold int `json:"blacklistThreshold"`
|
||||
MaxRetries int `json:"maxRetries"` // 最大重试次数
|
||||
}
|
||||
|
||||
// OpenAIConfig OpenAI API 配置
|
||||
@@ -76,10 +77,11 @@ type PerformanceConfig struct {
|
||||
|
||||
// LogConfig 日志配置
|
||||
type LogConfig struct {
|
||||
Level string `json:"level"` // debug, info, warn, error
|
||||
Format string `json:"format"` // text, json
|
||||
EnableFile bool `json:"enableFile"` // 是否启用文件日志
|
||||
FilePath string `json:"filePath"` // 日志文件路径
|
||||
Level string `json:"level"` // debug, info, warn, error
|
||||
Format string `json:"format"` // text, json
|
||||
EnableFile bool `json:"enableFile"` // 是否启用文件日志
|
||||
FilePath string `json:"filePath"` // 日志文件路径
|
||||
EnableRequest bool `json:"enableRequest"` // 是否启用请求日志
|
||||
}
|
||||
|
||||
// Config 应用配置
|
||||
@@ -112,6 +114,7 @@ func LoadConfig() (*Config, error) {
|
||||
FilePath: getEnvOrDefault("KEYS_FILE", "keys.txt"),
|
||||
StartIndex: parseInteger(os.Getenv("START_INDEX"), 0),
|
||||
BlacklistThreshold: parseInteger(os.Getenv("BLACKLIST_THRESHOLD"), 1),
|
||||
MaxRetries: parseInteger(os.Getenv("MAX_RETRIES"), 3),
|
||||
},
|
||||
OpenAI: OpenAIConfig{
|
||||
BaseURL: getEnvOrDefault("OPENAI_BASE_URL", "https://api.openai.com"),
|
||||
@@ -133,10 +136,11 @@ func LoadConfig() (*Config, error) {
|
||||
BufferSize: parseInteger(os.Getenv("BUFFER_SIZE"), 32*1024),
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: getEnvOrDefault("LOG_LEVEL", "info"),
|
||||
Format: getEnvOrDefault("LOG_FORMAT", "text"),
|
||||
EnableFile: parseBoolean(os.Getenv("LOG_ENABLE_FILE"), false),
|
||||
FilePath: getEnvOrDefault("LOG_FILE_PATH", "logs/app.log"),
|
||||
Level: getEnvOrDefault("LOG_LEVEL", "info"),
|
||||
Format: getEnvOrDefault("LOG_FORMAT", "text"),
|
||||
EnableFile: parseBoolean(os.Getenv("LOG_ENABLE_FILE"), false),
|
||||
FilePath: getEnvOrDefault("LOG_FILE_PATH", "logs/app.log"),
|
||||
EnableRequest: parseBoolean(os.Getenv("LOG_ENABLE_REQUEST"), true),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -205,6 +209,7 @@ func DisplayConfig(config *Config) {
|
||||
logrus.Infof(" 密钥文件: %s", config.Keys.FilePath)
|
||||
logrus.Infof(" 起始索引: %d", config.Keys.StartIndex)
|
||||
logrus.Infof(" 黑名单阈值: %d 次错误", config.Keys.BlacklistThreshold)
|
||||
logrus.Infof(" 最大重试次数: %d", config.Keys.MaxRetries)
|
||||
logrus.Infof(" 上游地址: %s", config.OpenAI.BaseURL)
|
||||
logrus.Infof(" 请求超时: %dms", config.OpenAI.Timeout)
|
||||
|
||||
@@ -233,6 +238,13 @@ func DisplayConfig(config *Config) {
|
||||
}
|
||||
logrus.Infof(" 压缩: %s", compressionStatus)
|
||||
logrus.Infof(" 缓冲区大小: %d bytes", config.Performance.BufferSize)
|
||||
|
||||
// 显示日志配置
|
||||
requestLogStatus := "已启用"
|
||||
if !config.Log.EnableRequest {
|
||||
requestLogStatus = "已禁用"
|
||||
}
|
||||
logrus.Infof(" 请求日志: %s", requestLogStatus)
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
|
@@ -193,10 +193,11 @@ func (ps *ProxyServer) authMiddleware() gin.HandlerFunc {
|
||||
// loggerMiddleware 高性能日志中间件
|
||||
func (ps *ProxyServer) loggerMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 只在非生产环境或调试模式下记录详细日志
|
||||
if gin.Mode() == gin.ReleaseMode {
|
||||
// 生产模式下只记录错误和关键信息
|
||||
// 检查是否启用请求日志
|
||||
if !config.AppConfig.Log.EnableRequest {
|
||||
// 不记录请求日志,只处理请求
|
||||
c.Next()
|
||||
// 只记录错误
|
||||
if c.Writer.Status() >= 400 {
|
||||
logrus.Errorf("Error %d: %s %s", c.Writer.Status(), c.Request.Method, c.Request.URL.Path)
|
||||
}
|
||||
@@ -231,8 +232,20 @@ func (ps *ProxyServer) loggerMiddleware() gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// 简化日志输出,减少格式化开销
|
||||
logrus.Infof("%s %s - %d - %v%s", method, fullPath, statusCode, latency, keyInfo)
|
||||
// 获取重试信息(如果存在)
|
||||
retryInfo := ""
|
||||
if retryCount, exists := c.Get("retryCount"); exists {
|
||||
retryInfo = fmt.Sprintf(" - Retry[%d]", retryCount)
|
||||
}
|
||||
|
||||
// 根据状态码选择日志级别
|
||||
if statusCode >= 500 {
|
||||
logrus.Errorf("%s %s - %d - %v%s%s", method, fullPath, statusCode, latency, keyInfo, retryInfo)
|
||||
} else if statusCode >= 400 {
|
||||
logrus.Warnf("%s %s - %d - %v%s%s", method, fullPath, statusCode, latency, keyInfo, retryInfo)
|
||||
} else {
|
||||
logrus.Infof("%s %s - %d - %v%s%s", method, fullPath, statusCode, latency, keyInfo, retryInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +309,31 @@ func (ps *ProxyServer) handleProxy(c *gin.Context) {
|
||||
// 增加请求计数
|
||||
atomic.AddInt64(&ps.requestCount, 1)
|
||||
|
||||
// 读取请求体用于重试
|
||||
var bodyBytes []byte
|
||||
if c.Request.Body != nil {
|
||||
var err error
|
||||
bodyBytes, err = io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
logrus.Errorf("读取请求体失败: %v", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": gin.H{
|
||||
"message": "读取请求体失败",
|
||||
"type": "request_error",
|
||||
"code": "invalid_request_body",
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 执行带重试的请求
|
||||
ps.executeRequestWithRetry(c, startTime, bodyBytes, 0)
|
||||
}
|
||||
|
||||
// executeRequestWithRetry 执行带重试的请求
|
||||
func (ps *ProxyServer) executeRequestWithRetry(c *gin.Context, startTime time.Time, bodyBytes []byte, retryCount int) {
|
||||
// 获取密钥信息
|
||||
keyInfo, err := ps.keyManager.GetNextKey()
|
||||
if err != nil {
|
||||
@@ -315,9 +353,18 @@ func (ps *ProxyServer) handleProxy(c *gin.Context) {
|
||||
c.Set("keyIndex", keyInfo.Index)
|
||||
c.Set("keyPreview", keyInfo.Preview)
|
||||
|
||||
// 直接使用请求体,避免完整读取到内存
|
||||
var requestBody io.Reader = c.Request.Body
|
||||
var contentLength int64 = c.Request.ContentLength
|
||||
// 设置重试信息到上下文
|
||||
if retryCount > 0 {
|
||||
c.Set("retryCount", retryCount)
|
||||
}
|
||||
|
||||
// 使用缓存的请求体
|
||||
var requestBody io.Reader
|
||||
var contentLength int64
|
||||
if len(bodyBytes) > 0 {
|
||||
requestBody = strings.NewReader(string(bodyBytes))
|
||||
contentLength = int64(len(bodyBytes))
|
||||
}
|
||||
|
||||
// 构建上游请求URL
|
||||
targetURL := *ps.upstreamURL
|
||||
@@ -368,14 +415,28 @@ func (ps *ProxyServer) handleProxy(c *gin.Context) {
|
||||
resp, err := ps.httpClient.Do(req)
|
||||
if err != nil {
|
||||
responseTime := time.Since(startTime)
|
||||
logrus.Errorf("代理请求失败: %v (响应时间: %v)", err, responseTime)
|
||||
|
||||
// 记录失败日志
|
||||
if retryCount > 0 {
|
||||
logrus.Warnf("重试请求失败 (第 %d 次): %v (响应时间: %v)", retryCount, err, responseTime)
|
||||
} else {
|
||||
logrus.Warnf("请求失败: %v (响应时间: %v)", err, responseTime)
|
||||
}
|
||||
|
||||
// 异步记录失败
|
||||
go ps.keyManager.RecordFailure(keyInfo.Key, err)
|
||||
|
||||
// 检查是否可以重试
|
||||
if retryCount < config.AppConfig.Keys.MaxRetries {
|
||||
logrus.Infof("准备重试请求 (第 %d/%d 次)", retryCount+1, config.AppConfig.Keys.MaxRetries)
|
||||
ps.executeRequestWithRetry(c, startTime, bodyBytes, retryCount+1)
|
||||
return
|
||||
}
|
||||
|
||||
// 达到最大重试次数
|
||||
c.JSON(http.StatusBadGateway, gin.H{
|
||||
"error": gin.H{
|
||||
"message": "代理请求失败: " + err.Error(),
|
||||
"message": fmt.Sprintf("代理请求失败 (已重试 %d 次): %s", retryCount, err.Error()),
|
||||
"type": "proxy_error",
|
||||
"code": "request_failed",
|
||||
"timestamp": time.Now().Format(time.RFC3339),
|
||||
@@ -387,6 +448,27 @@ func (ps *ProxyServer) handleProxy(c *gin.Context) {
|
||||
|
||||
responseTime := time.Since(startTime)
|
||||
|
||||
// 检查HTTP状态码是否需要重试
|
||||
// 429 (Too Many Requests) 和 5xx 服务器错误都需要重试
|
||||
if (resp.StatusCode == 429 || resp.StatusCode >= 500) && retryCount < config.AppConfig.Keys.MaxRetries {
|
||||
// 记录失败日志
|
||||
if retryCount > 0 {
|
||||
logrus.Warnf("重试请求返回错误 %d (第 %d 次) (响应时间: %v)", resp.StatusCode, retryCount, responseTime)
|
||||
} else {
|
||||
logrus.Warnf("请求返回错误 %d (响应时间: %v)", resp.StatusCode, responseTime)
|
||||
}
|
||||
|
||||
// 异步记录失败
|
||||
go ps.keyManager.RecordFailure(keyInfo.Key, fmt.Errorf("HTTP %d", resp.StatusCode))
|
||||
|
||||
// 关闭当前响应
|
||||
resp.Body.Close()
|
||||
|
||||
logrus.Infof("准备重试请求 (第 %d/%d 次)", retryCount+1, config.AppConfig.Keys.MaxRetries)
|
||||
ps.executeRequestWithRetry(c, startTime, bodyBytes, retryCount+1)
|
||||
return
|
||||
}
|
||||
|
||||
// 异步记录统计信息(不阻塞响应)
|
||||
go func() {
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 400 {
|
||||
|
Reference in New Issue
Block a user