From 2d9a7859aa3d5d3199316e9fd66429360de30976 Mon Sep 17 00:00:00 2001 From: tbphp Date: Sun, 3 Aug 2025 12:14:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20sqlite=E6=B5=8B=E8=AF=95=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E4=BC=98=E5=8C=96=20(#112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/keypool/provider.go | 33 +++++++++++++++++++++++++++++++-- internal/types/types.go | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/internal/keypool/provider.go b/internal/keypool/provider.go index 5a98368..83eb5e1 100644 --- a/internal/keypool/provider.go +++ b/internal/keypool/provider.go @@ -7,7 +7,9 @@ import ( app_errors "gpt-load/internal/errors" "gpt-load/internal/models" "gpt-load/internal/store" + "math/rand" "strconv" + "strings" "time" "github.com/sirupsen/logrus" @@ -88,6 +90,33 @@ func (p *KeyProvider) UpdateStatus(apiKey *models.APIKey, group *models.Group, i }() } +// executeTransactionWithRetry wraps a database transaction with a retry mechanism. +func (p *KeyProvider) executeTransactionWithRetry(operation func(tx *gorm.DB) error) error { + const maxRetries = 3 + const baseDelay = 50 * time.Millisecond + const maxJitter = 150 * time.Millisecond + var err error + + for i := range maxRetries { + err = p.db.Transaction(operation) + if err == nil { + return nil + } + + if strings.Contains(err.Error(), "database is locked") { + jitter := time.Duration(rand.Intn(int(maxJitter))) + totalDelay := baseDelay + jitter + logrus.Debugf("Database is locked, retrying in %v... (attempt %d/%d)", totalDelay, i+1, maxRetries) + time.Sleep(totalDelay) + continue + } + + break + } + + return err +} + func (p *KeyProvider) handleSuccess(keyID uint, keyHashKey, activeKeysListKey string) error { keyDetails, err := p.store.HGetAll(keyHashKey) if err != nil { @@ -101,7 +130,7 @@ func (p *KeyProvider) handleSuccess(keyID uint, keyHashKey, activeKeysListKey st return nil } - return p.db.Transaction(func(tx *gorm.DB) error { + return p.executeTransactionWithRetry(func(tx *gorm.DB) error { var key models.APIKey if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&key, keyID).Error; err != nil { return fmt.Errorf("failed to lock key %d for update: %w", keyID, err) @@ -149,7 +178,7 @@ func (p *KeyProvider) handleFailure(apiKey *models.APIKey, group *models.Group, // 获取该分组的有效配置 blacklistThreshold := group.EffectiveConfig.BlacklistThreshold - return p.db.Transaction(func(tx *gorm.DB) error { + return p.executeTransactionWithRetry(func(tx *gorm.DB) error { var key models.APIKey if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&key, apiKey.ID).Error; err != nil { return fmt.Errorf("failed to lock key %d for update: %w", apiKey.ID, err) diff --git a/internal/types/types.go b/internal/types/types.go index 122b877..fd34ec6 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -35,7 +35,7 @@ type SystemSettings struct { MaxRetries int `json:"max_retries" default:"3" name:"最大重试次数" category:"密钥配置" desc:"单个请求使用不同 Key 的最大重试次数,0为不重试。" validate:"min=0"` BlacklistThreshold int `json:"blacklist_threshold" default:"3" name:"黑名单阈值" category:"密钥配置" desc:"一个 Key 连续失败多少次后进入黑名单,0为不拉黑。" validate:"min=0"` KeyValidationIntervalMinutes int `json:"key_validation_interval_minutes" default:"60" name:"密钥验证间隔(分钟)" category:"密钥配置" desc:"后台验证密钥的默认间隔(分钟)。" validate:"min=30"` - KeyValidationConcurrency int `json:"key_validation_concurrency" default:"10" name:"密钥验证并发数" category:"密钥配置" desc:"后台定时验证无效 Key 时的并发数。" validate:"min=1"` + KeyValidationConcurrency int `json:"key_validation_concurrency" default:"10" name:"密钥验证并发数" category:"密钥配置" desc:"后台定时验证无效 Key 时的并发数,如果使用SQLite或者运行环境性能不佳,请尽量保证20以下,避免过高的并发导致数据不一致问题。" validate:"min=1"` KeyValidationTimeoutSeconds int `json:"key_validation_timeout_seconds" default:"20" name:"密钥验证超时(秒)" category:"密钥配置" desc:"后台定时验证单个 Key 时的 API 请求超时时间(秒)。" validate:"min=5"` // For cache