Compare commits
1 Commits
4b28684fe4
...
Branch_反向做
| Author | SHA1 | Date | |
|---|---|---|---|
| b1cca5bec7 |
@ -253,3 +253,30 @@ func (e LineReversePosition) ClosePositionBatch(c *gin.Context) {
|
||||
}
|
||||
e.OK(nil, "批量平仓成功")
|
||||
}
|
||||
|
||||
// 清除所有
|
||||
func (e LineReversePosition) CleanAll(c *gin.Context) {
|
||||
s := service.LineReversePosition{}
|
||||
|
||||
err := e.MakeContext(c).
|
||||
MakeOrm().
|
||||
MakeService(&s.Service).
|
||||
Errors
|
||||
|
||||
if err != nil {
|
||||
e.Logger.Error(err)
|
||||
e.Error(500, err, err.Error())
|
||||
return
|
||||
}
|
||||
userId := user.GetUserId(c)
|
||||
p := actions.GetPermissionFromContext(c)
|
||||
|
||||
err = s.CleanAll(p, userId)
|
||||
|
||||
if err != nil {
|
||||
e.Error(500, err, fmt.Sprintf("清除所有仓位失败,\r\n失败信息 %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
e.OK(nil, "清除所有仓位成功")
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ type LineReversePosition struct {
|
||||
ReverseStatus int `json:"reverseStatus" gorm:"type:tinyint;comment:反单仓位状态 1-已开仓 2-已平仓"`
|
||||
AveragePrice decimal.Decimal `json:"averagePrice" gorm:"type:decimal(18,8);comment:主单平均价格"`
|
||||
ReverseAveragePrice decimal.Decimal `json:"reverseAveragePrice" gorm:"type:decimal(18,8);comment:反单平均价格"`
|
||||
Version int `json:"version" gorm:"type:int;default:0;comment:版本号,用于乐观锁控制"`
|
||||
models.ModelTime
|
||||
models.ControlBy
|
||||
}
|
||||
|
||||
@ -22,6 +22,9 @@ func registerLineReversePositionRouter(v1 *gin.RouterGroup, authMiddleware *jwt.
|
||||
r.GET("/:id", actions.PermissionAction(), api.Get)
|
||||
r.POST("", api.Insert)
|
||||
r.PUT("/:id", actions.PermissionAction(), api.Update)
|
||||
|
||||
//清理所有
|
||||
r.DELETE("/clean-all", actions.PermissionAction(), api.CleanAll)
|
||||
r.DELETE("", api.Delete)
|
||||
|
||||
r.PUT("close/:id", actions.PermissionAction(), api.ClosePosition)
|
||||
|
||||
@ -25,6 +25,27 @@ type LineReversePosition struct {
|
||||
service.Service
|
||||
}
|
||||
|
||||
// 清除仓位记录
|
||||
func (e LineReversePosition) CleanAll(p *actions.DataPermission, userId int) error {
|
||||
var count int64
|
||||
|
||||
if err := e.Orm.Model(&models.LineReversePosition{}).
|
||||
Where("status =1 or reverse_status = 1").Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
e.Log.Errorf("还有仓位无法清除")
|
||||
return errors.New("有仓位正在进行中,不能清除所有")
|
||||
}
|
||||
|
||||
if err := e.Orm.Exec("TRUNCATE TABLE line_reverse_position").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 批量关闭仓位
|
||||
func (e LineReversePosition) ClosePositionBatch(req *dto.LineReversePositionCloseBatchReq, p *actions.DataPermission, userId int, errs *[]string) error {
|
||||
var positions []models.LineReversePosition
|
||||
|
||||
3
go.mod
3
go.mod
@ -39,6 +39,7 @@ require (
|
||||
github.com/shirou/gopsutil/v3 v3.23.10
|
||||
github.com/shopspring/decimal v1.2.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.2
|
||||
@ -82,6 +83,7 @@ require (
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
@ -143,6 +145,7 @@ require (
|
||||
github.com/nsqio/go-nsq v1.1.0 // indirect
|
||||
github.com/nyaruka/phonenumbers v1.0.55 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
func TestCancelFutClosePosition(t *testing.T) {
|
||||
dsn := "root:root@tcp(192.168.123.216:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
|
||||
dsn := "root:123456@tcp(127.0.0.1:3306)/gp-bian?charset=utf8mb4&parseTime=True&loc=Local&timeout=1000ms"
|
||||
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
var apiUserInfo models.LineApiUser
|
||||
db.Model(&models.LineApiUser{}).Where("id = 21").Find(&apiUserInfo)
|
||||
|
||||
358
services/binanceservice/config_optimized.go
Normal file
358
services/binanceservice/config_optimized.go
Normal file
@ -0,0 +1,358 @@
|
||||
package binanceservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// OptimizedConfig 优化配置结构
|
||||
type OptimizedConfig struct {
|
||||
// 锁配置
|
||||
LockConfig LockConfig `json:"lock_config"`
|
||||
|
||||
// 重试配置
|
||||
RetryConfig RetryConfig `json:"retry_config"`
|
||||
|
||||
// 熔断器配置
|
||||
CircuitBreakerConfig CircuitBreakerConfig `json:"circuit_breaker_config"`
|
||||
|
||||
// 同步配置
|
||||
SyncConfig SyncConfig `json:"sync_config"`
|
||||
|
||||
// 性能配置
|
||||
PerformanceConfig PerformanceConfig `json:"performance_config"`
|
||||
|
||||
// 监控配置
|
||||
MonitorConfig MonitorConfig `json:"monitor_config"`
|
||||
}
|
||||
|
||||
// LockConfig 锁配置
|
||||
type LockConfig struct {
|
||||
// Redis分布式锁超时时间
|
||||
RedisLockTimeout time.Duration `json:"redis_lock_timeout"`
|
||||
|
||||
// 获取锁等待时间
|
||||
LockWaitTimeout time.Duration `json:"lock_wait_timeout"`
|
||||
|
||||
// 订单处理锁前缀
|
||||
OrderLockPrefix string `json:"order_lock_prefix"`
|
||||
|
||||
// 持仓更新锁前缀
|
||||
PositionLockPrefix string `json:"position_lock_prefix"`
|
||||
|
||||
// 止盈止损锁前缀
|
||||
TakeProfitLockPrefix string `json:"take_profit_lock_prefix"`
|
||||
}
|
||||
|
||||
// CircuitBreakerConfig 熔断器配置
|
||||
type CircuitBreakerConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
FailureThreshold int `json:"failure_threshold"`
|
||||
SuccessThreshold int `json:"success_threshold"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
HalfOpenMaxCalls int `json:"half_open_max_calls"`
|
||||
}
|
||||
|
||||
// RetryConfig 重试配置
|
||||
type RetryConfig struct {
|
||||
// 最大重试次数
|
||||
MaxRetries int `json:"max_retries"`
|
||||
|
||||
// 重试延迟
|
||||
RetryDelay time.Duration `json:"retry_delay"`
|
||||
|
||||
// 指数退避因子
|
||||
BackoffFactor float64 `json:"backoff_factor"`
|
||||
|
||||
// 最大重试延迟
|
||||
MaxRetryDelay time.Duration `json:"max_retry_delay"`
|
||||
|
||||
// API调用重试次数
|
||||
ApiRetryCount int `json:"api_retry_count"`
|
||||
|
||||
// 数据库操作重试次数
|
||||
DbRetryCount int `json:"db_retry_count"`
|
||||
}
|
||||
|
||||
// SyncConfig 同步配置
|
||||
type SyncConfig struct {
|
||||
// 持仓同步检查间隔
|
||||
PositionSyncInterval time.Duration `json:"position_sync_interval"`
|
||||
|
||||
// 持仓差异阈值
|
||||
PositionDiffThreshold decimal.Decimal `json:"position_diff_threshold"`
|
||||
|
||||
// 强制同步阈值
|
||||
ForceSyncThreshold decimal.Decimal `json:"force_sync_threshold"`
|
||||
|
||||
// 同步超时时间
|
||||
SyncTimeout time.Duration `json:"sync_timeout"`
|
||||
|
||||
// 是否启用自动同步
|
||||
AutoSyncEnabled bool `json:"auto_sync_enabled"`
|
||||
}
|
||||
|
||||
// PerformanceConfig 性能配置
|
||||
type PerformanceConfig struct {
|
||||
// 批量操作大小
|
||||
BatchSize int `json:"batch_size"`
|
||||
|
||||
// 异步处理队列大小
|
||||
AsyncQueueSize int `json:"async_queue_size"`
|
||||
|
||||
// 工作协程数量
|
||||
WorkerCount int `json:"worker_count"`
|
||||
|
||||
// 数据库连接池大小
|
||||
DbPoolSize int `json:"db_pool_size"`
|
||||
|
||||
// 缓存过期时间
|
||||
CacheExpiration time.Duration `json:"cache_expiration"`
|
||||
|
||||
// 是否启用缓存
|
||||
CacheEnabled bool `json:"cache_enabled"`
|
||||
}
|
||||
|
||||
// MonitorConfig 监控配置
|
||||
type MonitorConfig struct {
|
||||
// 是否启用监控
|
||||
Enabled bool `json:"enabled"`
|
||||
|
||||
// 监控数据收集间隔
|
||||
CollectInterval time.Duration `json:"collect_interval"`
|
||||
|
||||
// 告警阈值配置
|
||||
AlertThresholds AlertThresholds `json:"alert_thresholds"`
|
||||
|
||||
// 日志级别
|
||||
LogLevel string `json:"log_level"`
|
||||
|
||||
// 是否启用性能分析
|
||||
ProfileEnabled bool `json:"profile_enabled"`
|
||||
}
|
||||
|
||||
// AlertThresholds 告警阈值配置
|
||||
type AlertThresholds struct {
|
||||
// 订单处理失败率阈值 (百分比)
|
||||
OrderFailureRate float64 `json:"order_failure_rate"`
|
||||
|
||||
// 持仓同步失败率阈值 (百分比)
|
||||
PositionSyncFailureRate float64 `json:"position_sync_failure_rate"`
|
||||
|
||||
// API调用失败率阈值 (百分比)
|
||||
ApiFailureRate float64 `json:"api_failure_rate"`
|
||||
|
||||
// 响应时间阈值 (毫秒)
|
||||
ResponseTimeThreshold time.Duration `json:"response_time_threshold"`
|
||||
|
||||
// 内存使用率阈值 (百分比)
|
||||
MemoryUsageThreshold float64 `json:"memory_usage_threshold"`
|
||||
|
||||
// CPU使用率阈值 (百分比)
|
||||
CpuUsageThreshold float64 `json:"cpu_usage_threshold"`
|
||||
}
|
||||
|
||||
// GetDefaultOptimizedConfig 获取默认优化配置
|
||||
func GetDefaultOptimizedConfig() *OptimizedConfig {
|
||||
return &OptimizedConfig{
|
||||
LockConfig: LockConfig{
|
||||
RedisLockTimeout: 30 * time.Second,
|
||||
LockWaitTimeout: 5 * time.Second,
|
||||
OrderLockPrefix: "reverse_order_lock:",
|
||||
PositionLockPrefix: "position_update_lock:",
|
||||
TakeProfitLockPrefix: "take_profit_lock:",
|
||||
},
|
||||
RetryConfig: RetryConfig{
|
||||
MaxRetries: 3,
|
||||
RetryDelay: time.Second,
|
||||
BackoffFactor: 2.0,
|
||||
MaxRetryDelay: 10 * time.Second,
|
||||
ApiRetryCount: 3,
|
||||
DbRetryCount: 3,
|
||||
},
|
||||
CircuitBreakerConfig: CircuitBreakerConfig{
|
||||
Enabled: true,
|
||||
FailureThreshold: 5,
|
||||
SuccessThreshold: 3,
|
||||
Timeout: 60 * time.Second,
|
||||
HalfOpenMaxCalls: 3,
|
||||
},
|
||||
SyncConfig: SyncConfig{
|
||||
PositionSyncInterval: 30 * time.Second,
|
||||
PositionDiffThreshold: decimal.NewFromFloat(0.001),
|
||||
ForceSyncThreshold: decimal.NewFromFloat(0.01),
|
||||
SyncTimeout: 10 * time.Second,
|
||||
AutoSyncEnabled: true,
|
||||
},
|
||||
PerformanceConfig: PerformanceConfig{
|
||||
BatchSize: 10,
|
||||
AsyncQueueSize: 1000,
|
||||
WorkerCount: 5,
|
||||
DbPoolSize: 20,
|
||||
CacheExpiration: 5 * time.Minute,
|
||||
CacheEnabled: true,
|
||||
},
|
||||
MonitorConfig: MonitorConfig{
|
||||
Enabled: true,
|
||||
CollectInterval: time.Minute,
|
||||
AlertThresholds: AlertThresholds{
|
||||
OrderFailureRate: 5.0, // 5%
|
||||
PositionSyncFailureRate: 2.0, // 2%
|
||||
ApiFailureRate: 10.0, // 10%
|
||||
ResponseTimeThreshold: 5 * time.Second,
|
||||
MemoryUsageThreshold: 80.0, // 80%
|
||||
CpuUsageThreshold: 70.0, // 70%
|
||||
},
|
||||
LogLevel: "info",
|
||||
ProfileEnabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig 验证配置有效性
|
||||
func (c *OptimizedConfig) ValidateConfig() error {
|
||||
// 验证锁配置
|
||||
if c.LockConfig.RedisLockTimeout <= 0 {
|
||||
return fmt.Errorf("Redis锁超时时间必须大于0")
|
||||
}
|
||||
if c.LockConfig.LockWaitTimeout <= 0 {
|
||||
return fmt.Errorf("锁等待超时时间必须大于0")
|
||||
}
|
||||
|
||||
// 验证重试配置
|
||||
if c.RetryConfig.MaxRetries < 0 {
|
||||
return fmt.Errorf("最大重试次数不能为负数")
|
||||
}
|
||||
if c.RetryConfig.RetryDelay <= 0 {
|
||||
return fmt.Errorf("重试延迟必须大于0")
|
||||
}
|
||||
if c.RetryConfig.BackoffFactor <= 1.0 {
|
||||
return fmt.Errorf("退避因子必须大于1.0")
|
||||
}
|
||||
|
||||
// 验证同步配置
|
||||
if c.SyncConfig.PositionSyncInterval <= 0 {
|
||||
return fmt.Errorf("持仓同步间隔必须大于0")
|
||||
}
|
||||
if c.SyncConfig.PositionDiffThreshold.IsNegative() {
|
||||
return fmt.Errorf("持仓差异阈值不能为负数")
|
||||
}
|
||||
|
||||
// 验证性能配置
|
||||
if c.PerformanceConfig.BatchSize <= 0 {
|
||||
return fmt.Errorf("批量操作大小必须大于0")
|
||||
}
|
||||
if c.PerformanceConfig.WorkerCount <= 0 {
|
||||
return fmt.Errorf("工作协程数量必须大于0")
|
||||
}
|
||||
if c.PerformanceConfig.DbPoolSize <= 0 {
|
||||
return fmt.Errorf("数据库连接池大小必须大于0")
|
||||
}
|
||||
|
||||
// 验证监控配置
|
||||
if c.MonitorConfig.Enabled {
|
||||
if c.MonitorConfig.CollectInterval <= 0 {
|
||||
return fmt.Errorf("监控数据收集间隔必须大于0")
|
||||
}
|
||||
if c.MonitorConfig.AlertThresholds.OrderFailureRate < 0 || c.MonitorConfig.AlertThresholds.OrderFailureRate > 100 {
|
||||
return fmt.Errorf("订单失败率阈值必须在0-100之间")
|
||||
}
|
||||
if c.MonitorConfig.AlertThresholds.MemoryUsageThreshold < 0 || c.MonitorConfig.AlertThresholds.MemoryUsageThreshold > 100 {
|
||||
return fmt.Errorf("内存使用率阈值必须在0-100之间")
|
||||
}
|
||||
if c.MonitorConfig.AlertThresholds.CpuUsageThreshold < 0 || c.MonitorConfig.AlertThresholds.CpuUsageThreshold > 100 {
|
||||
return fmt.Errorf("CPU使用率阈值必须在0-100之间")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLockKey 生成锁键名
|
||||
func (c *OptimizedConfig) GetLockKey(lockType, identifier string) string {
|
||||
switch lockType {
|
||||
case "order":
|
||||
return c.LockConfig.OrderLockPrefix + identifier
|
||||
case "position":
|
||||
return c.LockConfig.PositionLockPrefix + identifier
|
||||
case "take_profit":
|
||||
return c.LockConfig.TakeProfitLockPrefix + identifier
|
||||
default:
|
||||
return "unknown_lock:" + identifier
|
||||
}
|
||||
}
|
||||
|
||||
// IsRetryableError 判断错误是否可重试
|
||||
func (c *OptimizedConfig) IsRetryableError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errStr := err.Error()
|
||||
// 定义不可重试的错误类型
|
||||
nonRetryableErrors := []string{
|
||||
"余额不足",
|
||||
"订单重复",
|
||||
"API-key",
|
||||
"无效",
|
||||
"权限",
|
||||
"签名",
|
||||
"参数错误",
|
||||
}
|
||||
|
||||
for _, nonRetryable := range nonRetryableErrors {
|
||||
if contains(errStr, []string{nonRetryable}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetRetryDelay 计算重试延迟时间
|
||||
func (c *OptimizedConfig) GetRetryDelay(attempt int) time.Duration {
|
||||
if attempt <= 0 {
|
||||
return c.RetryConfig.RetryDelay
|
||||
}
|
||||
|
||||
// 指数退避算法
|
||||
delay := c.RetryConfig.RetryDelay
|
||||
for i := 0; i < attempt; i++ {
|
||||
delay = time.Duration(float64(delay) * c.RetryConfig.BackoffFactor)
|
||||
if delay > c.RetryConfig.MaxRetryDelay {
|
||||
return c.RetryConfig.MaxRetryDelay
|
||||
}
|
||||
}
|
||||
|
||||
return delay
|
||||
}
|
||||
|
||||
// ShouldSync 判断是否需要同步持仓
|
||||
func (c *OptimizedConfig) ShouldSync(exchangePosition, systemPosition decimal.Decimal) bool {
|
||||
diff := exchangePosition.Sub(systemPosition).Abs()
|
||||
return diff.GreaterThan(c.SyncConfig.PositionDiffThreshold)
|
||||
}
|
||||
|
||||
// ShouldForceSync 判断是否需要强制同步
|
||||
func (c *OptimizedConfig) ShouldForceSync(exchangePosition, systemPosition decimal.Decimal) bool {
|
||||
diff := exchangePosition.Sub(systemPosition).Abs()
|
||||
return diff.GreaterThan(c.SyncConfig.ForceSyncThreshold)
|
||||
}
|
||||
|
||||
// 全局配置实例
|
||||
var GlobalOptimizedConfig *OptimizedConfig
|
||||
|
||||
// InitOptimizedConfig 初始化优化配置
|
||||
func InitOptimizedConfig() error {
|
||||
GlobalOptimizedConfig = GetDefaultOptimizedConfig()
|
||||
return GlobalOptimizedConfig.ValidateConfig()
|
||||
}
|
||||
|
||||
// GetOptimizedConfig 获取全局优化配置
|
||||
func GetOptimizedConfig() *OptimizedConfig {
|
||||
if GlobalOptimizedConfig == nil {
|
||||
InitOptimizedConfig()
|
||||
}
|
||||
return GlobalOptimizedConfig
|
||||
}
|
||||
676
services/binanceservice/error_handler_optimized.go
Normal file
676
services/binanceservice/error_handler_optimized.go
Normal file
@ -0,0 +1,676 @@
|
||||
package binanceservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ErrorType 错误类型
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
ErrorTypeNetwork ErrorType = "network"
|
||||
ErrorTypeDatabase ErrorType = "database"
|
||||
ErrorTypeBusiness ErrorType = "business"
|
||||
ErrorTypeSystem ErrorType = "system"
|
||||
ErrorTypeValidation ErrorType = "validation"
|
||||
ErrorTypeTimeout ErrorType = "timeout"
|
||||
ErrorTypeRateLimit ErrorType = "rate_limit"
|
||||
ErrorTypeAuth ErrorType = "auth"
|
||||
)
|
||||
|
||||
// ErrorSeverity 错误严重程度
|
||||
type ErrorSeverity string
|
||||
|
||||
const (
|
||||
SeverityLow ErrorSeverity = "low"
|
||||
SeverityMedium ErrorSeverity = "medium"
|
||||
SeverityHigh ErrorSeverity = "high"
|
||||
SeverityCritical ErrorSeverity = "critical"
|
||||
)
|
||||
|
||||
// ErrorAction 错误处理动作
|
||||
type ErrorAction string
|
||||
|
||||
const (
|
||||
ActionRetry ErrorAction = "retry"
|
||||
ActionFallback ErrorAction = "fallback"
|
||||
ActionAlert ErrorAction = "alert"
|
||||
ActionIgnore ErrorAction = "ignore"
|
||||
ActionCircuit ErrorAction = "circuit"
|
||||
ActionDegrade ErrorAction = "degrade"
|
||||
)
|
||||
|
||||
// ErrorInfo 错误信息
|
||||
type ErrorInfo struct {
|
||||
Type ErrorType `json:"type"`
|
||||
Severity ErrorSeverity `json:"severity"`
|
||||
Message string `json:"message"`
|
||||
OriginalErr error `json:"-"`
|
||||
Context string `json:"context"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Retryable bool `json:"retryable"`
|
||||
Action ErrorAction `json:"action"`
|
||||
}
|
||||
|
||||
// RetryConfig 和 CircuitBreakerConfig 已在 config_optimized.go 中定义
|
||||
|
||||
// ErrorHandler 错误处理器
|
||||
type ErrorHandler struct {
|
||||
config *OptimizedConfig
|
||||
metricsCollector *MetricsCollector
|
||||
circuitBreakers map[string]*CircuitBreaker
|
||||
}
|
||||
|
||||
// CircuitBreaker 熔断器
|
||||
type CircuitBreaker struct {
|
||||
name string
|
||||
config CircuitBreakerConfig
|
||||
state CircuitState
|
||||
failureCount int
|
||||
successCount int
|
||||
lastFailTime time.Time
|
||||
nextRetry time.Time
|
||||
halfOpenCalls int
|
||||
}
|
||||
|
||||
// CircuitState 熔断器状态
|
||||
type CircuitState string
|
||||
|
||||
const (
|
||||
StateClosed CircuitState = "closed"
|
||||
StateOpen CircuitState = "open"
|
||||
StateHalfOpen CircuitState = "half_open"
|
||||
)
|
||||
|
||||
// NewErrorHandler 创建错误处理器
|
||||
func NewErrorHandler(config *OptimizedConfig, metricsCollector *MetricsCollector) *ErrorHandler {
|
||||
return &ErrorHandler{
|
||||
config: config,
|
||||
metricsCollector: metricsCollector,
|
||||
circuitBreakers: make(map[string]*CircuitBreaker),
|
||||
}
|
||||
}
|
||||
|
||||
// ClassifyError 分类错误
|
||||
func (eh *ErrorHandler) ClassifyError(err error, context string) *ErrorInfo {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
errorInfo := &ErrorInfo{
|
||||
OriginalErr: err,
|
||||
Message: err.Error(),
|
||||
Context: context,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// 根据错误类型分类
|
||||
switch {
|
||||
case isNetworkError(err):
|
||||
errorInfo.Type = ErrorTypeNetwork
|
||||
errorInfo.Severity = SeverityMedium
|
||||
errorInfo.Retryable = true
|
||||
errorInfo.Action = ActionRetry
|
||||
|
||||
case isDatabaseError(err):
|
||||
errorInfo.Type = ErrorTypeDatabase
|
||||
errorInfo.Severity = SeverityHigh
|
||||
errorInfo.Retryable = isRetryableDatabaseError(err)
|
||||
errorInfo.Action = ActionRetry
|
||||
|
||||
case isTimeoutError(err):
|
||||
errorInfo.Type = ErrorTypeTimeout
|
||||
errorInfo.Severity = SeverityMedium
|
||||
errorInfo.Retryable = true
|
||||
errorInfo.Action = ActionRetry
|
||||
|
||||
case isRateLimitError(err):
|
||||
errorInfo.Type = ErrorTypeRateLimit
|
||||
errorInfo.Severity = SeverityMedium
|
||||
errorInfo.Retryable = true
|
||||
errorInfo.Action = ActionRetry
|
||||
|
||||
case isAuthError(err):
|
||||
errorInfo.Type = ErrorTypeAuth
|
||||
errorInfo.Severity = SeverityHigh
|
||||
errorInfo.Retryable = false
|
||||
errorInfo.Action = ActionAlert
|
||||
|
||||
case isValidationError(err):
|
||||
errorInfo.Type = ErrorTypeValidation
|
||||
errorInfo.Severity = SeverityLow
|
||||
errorInfo.Retryable = false
|
||||
errorInfo.Action = ActionIgnore
|
||||
|
||||
case isBusinessError(err):
|
||||
errorInfo.Type = ErrorTypeBusiness
|
||||
errorInfo.Severity = SeverityMedium
|
||||
errorInfo.Retryable = false
|
||||
errorInfo.Action = ActionFallback
|
||||
|
||||
default:
|
||||
errorInfo.Type = ErrorTypeSystem
|
||||
errorInfo.Severity = SeverityHigh
|
||||
errorInfo.Retryable = true
|
||||
errorInfo.Action = ActionRetry
|
||||
}
|
||||
|
||||
return errorInfo
|
||||
}
|
||||
|
||||
// HandleError 处理错误
|
||||
func (eh *ErrorHandler) HandleError(err error, context string) *ErrorInfo {
|
||||
errorInfo := eh.ClassifyError(err, context)
|
||||
if errorInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 记录错误指标
|
||||
if eh.metricsCollector != nil {
|
||||
eh.metricsCollector.RecordError(string(errorInfo.Type), errorInfo.Message, context)
|
||||
}
|
||||
|
||||
// 根据错误动作处理
|
||||
switch errorInfo.Action {
|
||||
case ActionAlert:
|
||||
eh.sendAlert(errorInfo)
|
||||
case ActionCircuit:
|
||||
eh.triggerCircuitBreaker(context)
|
||||
case ActionDegrade:
|
||||
eh.enableDegradedMode(context)
|
||||
}
|
||||
|
||||
// 记录日志
|
||||
eh.logError(errorInfo)
|
||||
|
||||
return errorInfo
|
||||
}
|
||||
|
||||
// RetryWithBackoff 带退避的重试
|
||||
func (eh *ErrorHandler) RetryWithBackoff(ctx context.Context, operation func() error, config RetryConfig, context string) error {
|
||||
var lastErr error
|
||||
delay := config.RetryDelay
|
||||
|
||||
for attempt := 1; attempt <= config.MaxRetries; attempt++ {
|
||||
// 检查上下文是否已取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
// 检查熔断器状态
|
||||
if !eh.canExecute(context) {
|
||||
return fmt.Errorf("circuit breaker is open for %s", context)
|
||||
}
|
||||
|
||||
// 执行操作
|
||||
// 注意:config中没有TimeoutPerTry字段,这里暂时注释掉
|
||||
// if config.TimeoutPerTry > 0 {
|
||||
// var cancel context.CancelFunc
|
||||
// operationCtx, cancel = context.WithTimeout(ctx, config.TimeoutPerTry)
|
||||
// defer cancel()
|
||||
// }
|
||||
|
||||
err := operation()
|
||||
if err == nil {
|
||||
// 成功,记录成功指标
|
||||
if eh.metricsCollector != nil {
|
||||
eh.metricsCollector.RecordRetry(true)
|
||||
}
|
||||
eh.recordSuccess(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
errorInfo := eh.ClassifyError(err, context)
|
||||
|
||||
// 记录失败
|
||||
eh.recordFailure(context)
|
||||
|
||||
// 如果不可重试,直接返回
|
||||
if errorInfo != nil && !errorInfo.Retryable {
|
||||
logger.Warnf("不可重试的错误 [%s]: %v", context, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 最后一次尝试,不再等待
|
||||
if attempt == config.MaxRetries {
|
||||
break
|
||||
}
|
||||
|
||||
// 计算下次重试延迟
|
||||
nextDelay := eh.calculateDelay(delay, config)
|
||||
logger.Infof("重试 %d/%d [%s] 在 %v 后,错误: %v", attempt, config.MaxRetries, context, nextDelay, err)
|
||||
|
||||
// 等待重试
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(nextDelay):
|
||||
delay = nextDelay
|
||||
}
|
||||
}
|
||||
|
||||
// 记录重试失败指标
|
||||
if eh.metricsCollector != nil {
|
||||
eh.metricsCollector.RecordRetry(false)
|
||||
}
|
||||
|
||||
return fmt.Errorf("重试 %d 次后仍然失败 [%s]: %w", config.MaxRetries, context, lastErr)
|
||||
}
|
||||
|
||||
// calculateDelay 计算重试延迟
|
||||
func (eh *ErrorHandler) calculateDelay(currentDelay time.Duration, config RetryConfig) time.Duration {
|
||||
nextDelay := time.Duration(float64(currentDelay) * config.BackoffFactor)
|
||||
|
||||
// 限制最大延迟
|
||||
if nextDelay > config.MaxRetryDelay {
|
||||
nextDelay = config.MaxRetryDelay
|
||||
}
|
||||
|
||||
// 添加10%抖动(简化版本,不依赖JitterEnabled字段)
|
||||
jitter := time.Duration(float64(nextDelay) * 0.1)
|
||||
if jitter > 0 {
|
||||
nextDelay += time.Duration(time.Now().UnixNano() % int64(jitter))
|
||||
}
|
||||
|
||||
return nextDelay
|
||||
}
|
||||
|
||||
// canExecute 检查是否可以执行操作(熔断器检查)
|
||||
func (eh *ErrorHandler) canExecute(context string) bool {
|
||||
cb := eh.getCircuitBreaker(context)
|
||||
if cb == nil || !cb.config.Enabled {
|
||||
return true
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
switch cb.state {
|
||||
case StateClosed:
|
||||
return true
|
||||
|
||||
case StateOpen:
|
||||
if now.After(cb.nextRetry) {
|
||||
cb.state = StateHalfOpen
|
||||
cb.halfOpenCalls = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
case StateHalfOpen:
|
||||
return cb.halfOpenCalls < cb.config.HalfOpenMaxCalls
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// recordSuccess 记录成功
|
||||
func (eh *ErrorHandler) recordSuccess(context string) {
|
||||
cb := eh.getCircuitBreaker(context)
|
||||
if cb == nil || !cb.config.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
switch cb.state {
|
||||
case StateHalfOpen:
|
||||
cb.successCount++
|
||||
if cb.successCount >= cb.config.SuccessThreshold {
|
||||
cb.state = StateClosed
|
||||
cb.failureCount = 0
|
||||
cb.successCount = 0
|
||||
}
|
||||
|
||||
case StateClosed:
|
||||
cb.failureCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
// recordFailure 记录失败
|
||||
func (eh *ErrorHandler) recordFailure(context string) {
|
||||
cb := eh.getCircuitBreaker(context)
|
||||
if cb == nil || !cb.config.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
cb.failureCount++
|
||||
cb.lastFailTime = time.Now()
|
||||
|
||||
switch cb.state {
|
||||
case StateClosed:
|
||||
if cb.failureCount >= cb.config.FailureThreshold {
|
||||
cb.state = StateOpen
|
||||
cb.nextRetry = time.Now().Add(cb.config.Timeout)
|
||||
logger.Warnf("熔断器开启 [%s]: 失败次数 %d", context, cb.failureCount)
|
||||
}
|
||||
|
||||
case StateHalfOpen:
|
||||
cb.state = StateOpen
|
||||
cb.nextRetry = time.Now().Add(cb.config.Timeout)
|
||||
cb.successCount = 0
|
||||
logger.Warnf("熔断器重新开启 [%s]", context)
|
||||
}
|
||||
}
|
||||
|
||||
// getCircuitBreaker 获取熔断器
|
||||
func (eh *ErrorHandler) getCircuitBreaker(context string) *CircuitBreaker {
|
||||
cb, exists := eh.circuitBreakers[context]
|
||||
if !exists {
|
||||
cb = &CircuitBreaker{
|
||||
name: context,
|
||||
config: eh.config.CircuitBreakerConfig,
|
||||
state: StateClosed,
|
||||
}
|
||||
eh.circuitBreakers[context] = cb
|
||||
}
|
||||
return cb
|
||||
}
|
||||
|
||||
// triggerCircuitBreaker 触发熔断器
|
||||
func (eh *ErrorHandler) triggerCircuitBreaker(context string) {
|
||||
cb := eh.getCircuitBreaker(context)
|
||||
if cb != nil && cb.config.Enabled {
|
||||
cb.state = StateOpen
|
||||
cb.nextRetry = time.Now().Add(cb.config.Timeout)
|
||||
logger.Warnf("手动触发熔断器 [%s]", context)
|
||||
}
|
||||
}
|
||||
|
||||
// sendAlert 发送告警
|
||||
func (eh *ErrorHandler) sendAlert(errorInfo *ErrorInfo) {
|
||||
// 这里可以集成告警系统,如钉钉、邮件等
|
||||
logger.Errorf("告警: [%s] %s - %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
|
||||
}
|
||||
|
||||
// enableDegradedMode 启用降级模式
|
||||
func (eh *ErrorHandler) enableDegradedMode(context string) {
|
||||
logger.Warnf("启用降级模式 [%s]", context)
|
||||
// 这里可以实现具体的降级逻辑
|
||||
}
|
||||
|
||||
// logError 记录错误日志
|
||||
func (eh *ErrorHandler) logError(errorInfo *ErrorInfo) {
|
||||
switch errorInfo.Severity {
|
||||
case SeverityLow:
|
||||
logger.Infof("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
|
||||
case SeverityMedium:
|
||||
logger.Warnf("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
|
||||
case SeverityHigh, SeverityCritical:
|
||||
logger.Errorf("[%s] %s: %s", errorInfo.Type, errorInfo.Context, errorInfo.Message)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCircuitBreakerStatus 获取熔断器状态
|
||||
func (eh *ErrorHandler) GetCircuitBreakerStatus() map[string]interface{} {
|
||||
status := make(map[string]interface{})
|
||||
for name, cb := range eh.circuitBreakers {
|
||||
status[name] = map[string]interface{}{
|
||||
"state": cb.state,
|
||||
"failure_count": cb.failureCount,
|
||||
"success_count": cb.successCount,
|
||||
"last_fail_time": cb.lastFailTime,
|
||||
"next_retry": cb.nextRetry,
|
||||
}
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// 错误分类辅助函数
|
||||
|
||||
// isNetworkError 判断是否为网络错误
|
||||
func isNetworkError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查网络相关错误
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查常见网络错误消息
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
networkKeywords := []string{
|
||||
"connection refused",
|
||||
"connection reset",
|
||||
"connection timeout",
|
||||
"network unreachable",
|
||||
"no route to host",
|
||||
"dns",
|
||||
"socket",
|
||||
}
|
||||
|
||||
for _, keyword := range networkKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isDatabaseError 判断是否为数据库错误
|
||||
func isDatabaseError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查GORM错误
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) ||
|
||||
errors.Is(err, gorm.ErrInvalidTransaction) ||
|
||||
errors.Is(err, gorm.ErrNotImplemented) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查数据库相关错误消息
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
dbKeywords := []string{
|
||||
"database",
|
||||
"sql",
|
||||
"mysql",
|
||||
"postgres",
|
||||
"connection pool",
|
||||
"deadlock",
|
||||
"constraint",
|
||||
"duplicate key",
|
||||
}
|
||||
|
||||
for _, keyword := range dbKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isRetryableDatabaseError 判断是否为可重试的数据库错误
|
||||
func isRetryableDatabaseError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 不可重试的错误
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false
|
||||
}
|
||||
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
nonRetryableKeywords := []string{
|
||||
"constraint",
|
||||
"duplicate key",
|
||||
"foreign key",
|
||||
"syntax error",
|
||||
}
|
||||
|
||||
for _, keyword := range nonRetryableKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isTimeoutError 判断是否为超时错误
|
||||
func isTimeoutError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查超时错误
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
return true
|
||||
}
|
||||
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
return true
|
||||
}
|
||||
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
return strings.Contains(errorMsg, "timeout") ||
|
||||
strings.Contains(errorMsg, "deadline exceeded")
|
||||
}
|
||||
|
||||
// isRateLimitError 判断是否为限流错误
|
||||
func isRateLimitError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
rateLimitKeywords := []string{
|
||||
"rate limit",
|
||||
"too many requests",
|
||||
"429",
|
||||
"quota exceeded",
|
||||
"throttle",
|
||||
}
|
||||
|
||||
for _, keyword := range rateLimitKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isAuthError 判断是否为认证错误
|
||||
func isAuthError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
authKeywords := []string{
|
||||
"unauthorized",
|
||||
"authentication",
|
||||
"invalid signature",
|
||||
"api key",
|
||||
"401",
|
||||
"403",
|
||||
"forbidden",
|
||||
}
|
||||
|
||||
for _, keyword := range authKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isValidationError 判断是否为验证错误
|
||||
func isValidationError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
validationKeywords := []string{
|
||||
"validation",
|
||||
"invalid parameter",
|
||||
"bad request",
|
||||
"400",
|
||||
"missing required",
|
||||
"invalid format",
|
||||
}
|
||||
|
||||
for _, keyword := range validationKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isBusinessError 判断是否为业务错误
|
||||
func isBusinessError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errorMsg := strings.ToLower(err.Error())
|
||||
businessKeywords := []string{
|
||||
"insufficient balance",
|
||||
"order not found",
|
||||
"position not found",
|
||||
"invalid order status",
|
||||
"market closed",
|
||||
"symbol not found",
|
||||
}
|
||||
|
||||
for _, keyword := range businessKeywords {
|
||||
if strings.Contains(errorMsg, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 全局错误处理器实例
|
||||
var GlobalErrorHandler *ErrorHandler
|
||||
|
||||
// InitErrorHandler 初始化错误处理器
|
||||
func InitErrorHandler(config *OptimizedConfig, metricsCollector *MetricsCollector) {
|
||||
GlobalErrorHandler = NewErrorHandler(config, metricsCollector)
|
||||
}
|
||||
|
||||
// GetErrorHandler 获取全局错误处理器
|
||||
func GetErrorHandler() *ErrorHandler {
|
||||
return GlobalErrorHandler
|
||||
}
|
||||
|
||||
// HandleErrorWithRetry 处理错误并重试的便捷函数
|
||||
func HandleErrorWithRetry(ctx context.Context, operation func() error, context string) error {
|
||||
if GlobalErrorHandler == nil {
|
||||
return operation()
|
||||
}
|
||||
|
||||
config := RetryConfig{
|
||||
MaxRetries: 3,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
MaxRetryDelay: 5 * time.Second,
|
||||
BackoffFactor: 2.0,
|
||||
ApiRetryCount: 3,
|
||||
DbRetryCount: 3,
|
||||
}
|
||||
|
||||
return GlobalErrorHandler.RetryWithBackoff(ctx, operation, config, context)
|
||||
}
|
||||
587
services/binanceservice/integration_test_optimized.go
Normal file
587
services/binanceservice/integration_test_optimized.go
Normal file
@ -0,0 +1,587 @@
|
||||
package binanceservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OptimizedSystemTestSuite 优化系统测试套件
|
||||
type OptimizedSystemTestSuite struct {
|
||||
suite.Suite
|
||||
db *gorm.DB
|
||||
redisClient *redis.Client
|
||||
config *OptimizedConfig
|
||||
metricsCollector *MetricsCollector
|
||||
errorHandler *ErrorHandler
|
||||
lockManager *LockManager
|
||||
txManager *TransactionManager
|
||||
reverseService *ReverseServiceOptimized
|
||||
}
|
||||
|
||||
// SetupSuite 设置测试套件
|
||||
func (suite *OptimizedSystemTestSuite) SetupSuite() {
|
||||
// 初始化数据库连接(测试环境)
|
||||
dsn := "root:password@tcp(localhost:3306)/test_exchange?charset=utf8mb4&parseTime=True&loc=Local"
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
suite.T().Skip("跳过集成测试:无法连接数据库")
|
||||
return
|
||||
}
|
||||
suite.db = db
|
||||
|
||||
// 初始化Redis连接(测试环境)
|
||||
suite.redisClient = redis.NewClient(&redis.Options{
|
||||
Addr: "localhost:6379",
|
||||
Password: "",
|
||||
DB: 1, // 使用测试数据库
|
||||
})
|
||||
|
||||
// 测试Redis连接
|
||||
ctx := context.Background()
|
||||
if err := suite.redisClient.Ping(ctx).Err(); err != nil {
|
||||
suite.T().Skip("跳过集成测试:无法连接Redis")
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化配置
|
||||
suite.config = GetDefaultOptimizedConfig()
|
||||
suite.config.MonitorConfig.Enabled = true
|
||||
|
||||
// 初始化组件
|
||||
suite.metricsCollector = NewMetricsCollector(suite.config)
|
||||
suite.errorHandler = NewErrorHandler(suite.config, suite.metricsCollector)
|
||||
suite.lockManager = NewLockManager(suite.config, suite.redisClient, suite.metricsCollector)
|
||||
suite.txManager = NewTransactionManager(suite.db, suite.metricsCollector, suite.config)
|
||||
|
||||
// 启动指标收集
|
||||
suite.metricsCollector.Start()
|
||||
|
||||
// 创建测试表
|
||||
suite.createTestTables()
|
||||
}
|
||||
|
||||
// TearDownSuite 清理测试套件
|
||||
func (suite *OptimizedSystemTestSuite) TearDownSuite() {
|
||||
if suite.metricsCollector != nil {
|
||||
suite.metricsCollector.Stop()
|
||||
}
|
||||
if suite.redisClient != nil {
|
||||
suite.redisClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// SetupTest 设置每个测试
|
||||
func (suite *OptimizedSystemTestSuite) SetupTest() {
|
||||
// 清理Redis测试数据
|
||||
ctx := context.Background()
|
||||
suite.redisClient.FlushDB(ctx)
|
||||
|
||||
// 清理数据库测试数据
|
||||
suite.db.Exec("DELETE FROM test_orders")
|
||||
suite.db.Exec("DELETE FROM test_positions")
|
||||
}
|
||||
|
||||
// createTestTables 创建测试表
|
||||
func (suite *OptimizedSystemTestSuite) createTestTables() {
|
||||
// 创建测试订单表
|
||||
suite.db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS test_orders (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id VARCHAR(100) UNIQUE NOT NULL,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
symbol VARCHAR(20) NOT NULL,
|
||||
side VARCHAR(10) NOT NULL,
|
||||
quantity DECIMAL(20,8) NOT NULL,
|
||||
price DECIMAL(20,8),
|
||||
status VARCHAR(20) NOT NULL,
|
||||
version INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_user_symbol (user_id, symbol),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_version (version)
|
||||
)
|
||||
`)
|
||||
|
||||
// 创建测试持仓表
|
||||
suite.db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS test_positions (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id VARCHAR(50) NOT NULL,
|
||||
symbol VARCHAR(20) NOT NULL,
|
||||
side VARCHAR(10) NOT NULL,
|
||||
quantity DECIMAL(20,8) NOT NULL,
|
||||
avg_price DECIMAL(20,8),
|
||||
status VARCHAR(20) NOT NULL,
|
||||
version INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_user_symbol_side (user_id, symbol, side),
|
||||
INDEX idx_version (version)
|
||||
)
|
||||
`)
|
||||
}
|
||||
|
||||
// TestLockManager 测试锁管理器
|
||||
func (suite *OptimizedSystemTestSuite) TestLockManager() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 测试分布式锁
|
||||
suite.Run("DistributedLock", func() {
|
||||
config := LockManagerConfig{
|
||||
Type: LockTypeDistributed,
|
||||
Scope: ScopeOrder,
|
||||
Key: "test_order_123",
|
||||
Expiration: 10 * time.Second,
|
||||
Timeout: 5 * time.Second,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
// 获取锁
|
||||
lock, err := suite.lockManager.AcquireLock(ctx, config)
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.NotNil(suite.T(), lock)
|
||||
assert.True(suite.T(), lock.IsLocked())
|
||||
|
||||
// 尝试获取同一个锁(应该失败)
|
||||
config.Timeout = 1 * time.Second
|
||||
_, err = suite.lockManager.AcquireLock(ctx, config)
|
||||
assert.Error(suite.T(), err)
|
||||
|
||||
// 释放锁
|
||||
err = suite.lockManager.ReleaseLock(ctx, lock)
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.False(suite.T(), lock.IsLocked())
|
||||
})
|
||||
|
||||
// 测试本地锁
|
||||
suite.Run("LocalLock", func() {
|
||||
config := LockManagerConfig{
|
||||
Type: LockTypeLocal,
|
||||
Scope: ScopeUser,
|
||||
Key: "test_user_456",
|
||||
Expiration: 10 * time.Second,
|
||||
Timeout: 5 * time.Second,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
lock, err := suite.lockManager.AcquireLock(ctx, config)
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.NotNil(suite.T(), lock)
|
||||
assert.True(suite.T(), lock.IsLocked())
|
||||
|
||||
err = suite.lockManager.ReleaseLock(ctx, lock)
|
||||
assert.NoError(suite.T(), err)
|
||||
})
|
||||
|
||||
// 测试并发锁
|
||||
suite.Run("ConcurrentLocks", func() {
|
||||
var wg sync.WaitGroup
|
||||
successCount := 0
|
||||
var mu sync.Mutex
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
config := LockManagerConfig{
|
||||
Type: LockTypeDistributed,
|
||||
Scope: ScopeGlobal,
|
||||
Key: "concurrent_test",
|
||||
Expiration: 2 * time.Second,
|
||||
Timeout: 1 * time.Second,
|
||||
RetryDelay: 50 * time.Millisecond,
|
||||
}
|
||||
|
||||
lock, err := suite.lockManager.AcquireLock(ctx, config)
|
||||
if err == nil {
|
||||
mu.Lock()
|
||||
successCount++
|
||||
mu.Unlock()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
suite.lockManager.ReleaseLock(ctx, lock)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
assert.Equal(suite.T(), 1, successCount, "只有一个goroutine应该获得锁")
|
||||
})
|
||||
}
|
||||
|
||||
// TestTransactionManager 测试事务管理器
|
||||
func (suite *OptimizedSystemTestSuite) TestTransactionManager() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 测试基本事务
|
||||
suite.Run("BasicTransaction", func() {
|
||||
config := GetDefaultTransactionConfig()
|
||||
result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
// 插入测试数据
|
||||
return txCtx.Tx.Exec(`
|
||||
INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`, "order_001", "user_001", "BTCUSDT", "BUY", 1.0, "NEW").Error
|
||||
})
|
||||
|
||||
assert.True(suite.T(), result.Success)
|
||||
assert.NoError(suite.T(), result.Error)
|
||||
|
||||
// 验证数据已插入
|
||||
var count int64
|
||||
suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id = ?", "order_001").Scan(&count)
|
||||
assert.Equal(suite.T(), int64(1), count)
|
||||
})
|
||||
|
||||
// 测试事务回滚
|
||||
suite.Run("TransactionRollback", func() {
|
||||
config := GetDefaultTransactionConfig()
|
||||
result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
// 插入数据
|
||||
if err := txCtx.Tx.Exec(`
|
||||
INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`, "order_002", "user_002", "ETHUSDT", "SELL", 2.0, "NEW").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 故意返回错误以触发回滚
|
||||
return fmt.Errorf("测试回滚")
|
||||
})
|
||||
|
||||
assert.False(suite.T(), result.Success)
|
||||
assert.Error(suite.T(), result.Error)
|
||||
|
||||
// 验证数据未插入
|
||||
var count int64
|
||||
suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id = ?", "order_002").Scan(&count)
|
||||
assert.Equal(suite.T(), int64(0), count)
|
||||
})
|
||||
|
||||
// 测试乐观锁
|
||||
suite.Run("OptimisticLock", func() {
|
||||
// 先插入测试数据
|
||||
suite.db.Exec(`
|
||||
INSERT INTO test_positions (user_id, symbol, side, quantity, avg_price, status, version)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, "user_003", "BTCUSDT", "LONG", 1.0, 50000.0, "OPEN", 0)
|
||||
|
||||
// 测试正常更新
|
||||
updates := map[string]interface{}{
|
||||
"quantity": 2.0,
|
||||
}
|
||||
err := suite.txManager.UpdateWithOptimisticLock(ctx, nil, updates,
|
||||
"user_id = ? AND symbol = ? AND side = ? AND version = ?",
|
||||
"user_003", "BTCUSDT", "LONG", 0)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
// 测试版本冲突
|
||||
updates["quantity"] = 3.0
|
||||
err = suite.txManager.UpdateWithOptimisticLock(ctx, nil, updates,
|
||||
"user_id = ? AND symbol = ? AND side = ? AND version = ?",
|
||||
"user_003", "BTCUSDT", "LONG", 0) // 使用旧版本号
|
||||
assert.Error(suite.T(), err)
|
||||
assert.Contains(suite.T(), err.Error(), "乐观锁冲突")
|
||||
})
|
||||
}
|
||||
|
||||
// TestErrorHandler 测试错误处理器
|
||||
func (suite *OptimizedSystemTestSuite) TestErrorHandler() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 测试错误分类
|
||||
suite.Run("ErrorClassification", func() {
|
||||
testCases := []struct {
|
||||
error error
|
||||
expected ErrorType
|
||||
}{
|
||||
{fmt.Errorf("connection refused"), ErrorTypeNetwork},
|
||||
{fmt.Errorf("database connection failed"), ErrorTypeDatabase},
|
||||
{fmt.Errorf("context deadline exceeded"), ErrorTypeTimeout},
|
||||
{fmt.Errorf("too many requests"), ErrorTypeRateLimit},
|
||||
{fmt.Errorf("unauthorized access"), ErrorTypeAuth},
|
||||
{fmt.Errorf("invalid parameter"), ErrorTypeValidation},
|
||||
{fmt.Errorf("insufficient balance"), ErrorTypeBusiness},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
errorInfo := suite.errorHandler.ClassifyError(tc.error, "test")
|
||||
assert.Equal(suite.T(), tc.expected, errorInfo.Type)
|
||||
}
|
||||
})
|
||||
|
||||
// 测试重试机制
|
||||
suite.Run("RetryMechanism", func() {
|
||||
attempts := 0
|
||||
config := RetryConfig{
|
||||
MaxRetries: 3,
|
||||
RetryDelay: 10 * time.Millisecond,
|
||||
MaxRetryDelay: 100 * time.Millisecond,
|
||||
BackoffFactor: 2.0,
|
||||
ApiRetryCount: 3,
|
||||
DbRetryCount: 3,
|
||||
}
|
||||
|
||||
// 测试重试成功
|
||||
err := suite.errorHandler.RetryWithBackoff(ctx, func() error {
|
||||
attempts++
|
||||
if attempts < 3 {
|
||||
return fmt.Errorf("temporary error")
|
||||
}
|
||||
return nil
|
||||
}, config, "test_retry")
|
||||
|
||||
assert.NoError(suite.T(), err)
|
||||
assert.Equal(suite.T(), 3, attempts)
|
||||
|
||||
// 测试重试失败
|
||||
attempts = 0
|
||||
err = suite.errorHandler.RetryWithBackoff(ctx, func() error {
|
||||
attempts++
|
||||
return fmt.Errorf("persistent error")
|
||||
}, config, "test_retry_fail")
|
||||
|
||||
assert.Error(suite.T(), err)
|
||||
assert.Equal(suite.T(), 3, attempts)
|
||||
})
|
||||
}
|
||||
|
||||
// TestMetricsCollector 测试指标收集器
|
||||
func (suite *OptimizedSystemTestSuite) TestMetricsCollector() {
|
||||
// 测试订单指标
|
||||
suite.Run("OrderMetrics", func() {
|
||||
// 记录订单处理
|
||||
suite.metricsCollector.RecordOrderProcessed(true, 100*time.Millisecond)
|
||||
suite.metricsCollector.RecordOrderProcessed(false, 200*time.Millisecond)
|
||||
suite.metricsCollector.RecordOrderCanceled()
|
||||
suite.metricsCollector.RecordReverseOrder(true)
|
||||
|
||||
metrics := suite.metricsCollector.GetMetrics()
|
||||
orderMetrics := metrics["order_metrics"].(*OrderMetrics)
|
||||
|
||||
assert.Equal(suite.T(), int64(2), orderMetrics.TotalProcessed)
|
||||
assert.Equal(suite.T(), int64(1), orderMetrics.SuccessfulOrders)
|
||||
assert.Equal(suite.T(), int64(1), orderMetrics.FailedOrders)
|
||||
assert.Equal(suite.T(), int64(1), orderMetrics.CanceledOrders)
|
||||
assert.Equal(suite.T(), int64(1), orderMetrics.ReverseOrdersCreated)
|
||||
})
|
||||
|
||||
// 测试持仓指标
|
||||
suite.Run("PositionMetrics", func() {
|
||||
difference := decimal.NewFromFloat(0.1)
|
||||
suite.metricsCollector.RecordPositionSync(true, difference)
|
||||
suite.metricsCollector.RecordPositionSync(false, decimal.Zero)
|
||||
suite.metricsCollector.RecordClosePosition(false)
|
||||
suite.metricsCollector.RecordClosePosition(true)
|
||||
|
||||
metrics := suite.metricsCollector.GetMetrics()
|
||||
positionMetrics := metrics["position_metrics"].(*PositionMetrics)
|
||||
|
||||
assert.Equal(suite.T(), int64(2), positionMetrics.SyncAttempts)
|
||||
assert.Equal(suite.T(), int64(1), positionMetrics.SyncSuccesses)
|
||||
assert.Equal(suite.T(), int64(1), positionMetrics.SyncFailures)
|
||||
assert.Equal(suite.T(), int64(2), positionMetrics.ClosePositions)
|
||||
assert.Equal(suite.T(), int64(1), positionMetrics.ForceClosePositions)
|
||||
})
|
||||
|
||||
// 测试性能指标
|
||||
suite.Run("PerformanceMetrics", func() {
|
||||
suite.metricsCollector.RecordLockOperation(true, 50*time.Millisecond)
|
||||
suite.metricsCollector.RecordDbOperation(false, 30*time.Millisecond, nil)
|
||||
suite.metricsCollector.RecordApiCall(100*time.Millisecond, nil, 1)
|
||||
suite.metricsCollector.RecordConcurrency(5)
|
||||
|
||||
metrics := suite.metricsCollector.GetMetrics()
|
||||
perfMetrics := metrics["performance_metrics"].(*PerformanceMetrics)
|
||||
|
||||
assert.Equal(suite.T(), int64(1), perfMetrics.LockAcquisitions)
|
||||
assert.Equal(suite.T(), int64(1), perfMetrics.DbQueries)
|
||||
assert.Equal(suite.T(), int64(1), perfMetrics.ApiCalls)
|
||||
assert.Equal(suite.T(), int64(5), perfMetrics.ConcurrentRequests)
|
||||
})
|
||||
}
|
||||
|
||||
// TestConcurrentOperations 测试并发操作
|
||||
func (suite *OptimizedSystemTestSuite) TestConcurrentOperations() {
|
||||
ctx := context.Background()
|
||||
|
||||
suite.Run("ConcurrentOrderProcessing", func() {
|
||||
var wg sync.WaitGroup
|
||||
successCount := 0
|
||||
var mu sync.Mutex
|
||||
|
||||
// 并发处理订单
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
|
||||
// 使用锁保护订单处理
|
||||
lockConfig := GetOrderLockConfig(fmt.Sprintf("order_%d", id))
|
||||
err := suite.lockManager.WithLock(ctx, lockConfig, func() error {
|
||||
// 模拟订单处理
|
||||
config := GetDefaultTransactionConfig()
|
||||
result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
return txCtx.Tx.Exec(`
|
||||
INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`, fmt.Sprintf("concurrent_order_%d", id), fmt.Sprintf("user_%d", id),
|
||||
"BTCUSDT", "BUY", 1.0, "NEW").Error
|
||||
})
|
||||
|
||||
if result.Success {
|
||||
mu.Lock()
|
||||
successCount++
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
return result.Error
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("并发订单处理失败 [%d]: %v", id, err)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
assert.Equal(suite.T(), 10, successCount, "所有并发订单都应该成功处理")
|
||||
|
||||
// 验证数据库中的记录数
|
||||
var count int64
|
||||
suite.db.Raw("SELECT COUNT(*) FROM test_orders WHERE order_id LIKE 'concurrent_order_%'").Scan(&count)
|
||||
assert.Equal(suite.T(), int64(10), count)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSystemIntegration 测试系统集成
|
||||
func (suite *OptimizedSystemTestSuite) TestSystemIntegration() {
|
||||
ctx := context.Background()
|
||||
|
||||
suite.Run("CompleteOrderFlow", func() {
|
||||
userID := "integration_user"
|
||||
symbol := "BTCUSDT"
|
||||
orderID := "integration_order_001"
|
||||
|
||||
// 步骤1:创建订单(使用锁和事务)
|
||||
err := WithOrderLock(ctx, orderID, func() error {
|
||||
return WithDefaultTransaction(ctx, func(txCtx *TransactionContext) error {
|
||||
return txCtx.Tx.Exec(`
|
||||
INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, price, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`, orderID, userID, symbol, "BUY", 1.0, 50000.0, "NEW").Error
|
||||
})
|
||||
})
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
// 步骤2:更新订单状态(使用乐观锁)
|
||||
updates := map[string]interface{}{
|
||||
"status": "FILLED",
|
||||
}
|
||||
err = UpdateWithOptimisticLockGlobal(ctx, nil, updates,
|
||||
"order_id = ? AND version = ?", orderID, 0)
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
// 步骤3:创建持仓(使用锁和事务)
|
||||
err = WithPositionLock(ctx, userID, symbol, func() error {
|
||||
return WithDefaultTransaction(ctx, func(txCtx *TransactionContext) error {
|
||||
return txCtx.Tx.Exec(`
|
||||
INSERT INTO test_positions (user_id, symbol, side, quantity, avg_price, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
quantity = quantity + VALUES(quantity),
|
||||
avg_price = (avg_price * quantity + VALUES(avg_price) * VALUES(quantity)) / (quantity + VALUES(quantity)),
|
||||
version = version + 1
|
||||
`, userID, symbol, "LONG", 1.0, 50000.0, "OPEN").Error
|
||||
})
|
||||
})
|
||||
assert.NoError(suite.T(), err)
|
||||
|
||||
// 验证最终状态
|
||||
var orderStatus string
|
||||
suite.db.Raw("SELECT status FROM test_orders WHERE order_id = ?", orderID).Scan(&orderStatus)
|
||||
assert.Equal(suite.T(), "FILLED", orderStatus)
|
||||
|
||||
var positionQuantity float64
|
||||
suite.db.Raw("SELECT quantity FROM test_positions WHERE user_id = ? AND symbol = ?",
|
||||
userID, symbol).Scan(&positionQuantity)
|
||||
assert.Equal(suite.T(), 1.0, positionQuantity)
|
||||
})
|
||||
}
|
||||
|
||||
// TestPerformance 测试性能
|
||||
func (suite *OptimizedSystemTestSuite) TestPerformance() {
|
||||
ctx := context.Background()
|
||||
|
||||
suite.Run("LockPerformance", func() {
|
||||
startTime := time.Now()
|
||||
iterations := 1000
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
lockConfig := GetOrderLockConfig(fmt.Sprintf("perf_order_%d", i))
|
||||
lock, err := suite.lockManager.AcquireLock(ctx, lockConfig)
|
||||
assert.NoError(suite.T(), err)
|
||||
suite.lockManager.ReleaseLock(ctx, lock)
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
avgDuration := duration / time.Duration(iterations)
|
||||
logger.Infof("锁性能测试: %d 次操作耗时 %v,平均每次 %v", iterations, duration, avgDuration)
|
||||
|
||||
// 平均每次操作应该在合理范围内
|
||||
assert.Less(suite.T(), avgDuration, 10*time.Millisecond)
|
||||
})
|
||||
|
||||
suite.Run("TransactionPerformance", func() {
|
||||
startTime := time.Now()
|
||||
iterations := 100
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
config := GetDefaultTransactionConfig()
|
||||
result := suite.txManager.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
return txCtx.Tx.Exec(`
|
||||
INSERT INTO test_orders (order_id, user_id, symbol, side, quantity, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`, fmt.Sprintf("perf_order_%d", i), "perf_user", "BTCUSDT", "BUY", 1.0, "NEW").Error
|
||||
})
|
||||
assert.True(suite.T(), result.Success)
|
||||
}
|
||||
|
||||
duration := time.Since(startTime)
|
||||
avgDuration := duration / time.Duration(iterations)
|
||||
logger.Infof("事务性能测试: %d 次操作耗时 %v,平均每次 %v", iterations, duration, avgDuration)
|
||||
|
||||
// 平均每次事务应该在合理范围内
|
||||
assert.Less(suite.T(), avgDuration, 100*time.Millisecond)
|
||||
})
|
||||
}
|
||||
|
||||
// TestSuite 运行测试套件
|
||||
func TestOptimizedSystemSuite(t *testing.T) {
|
||||
suite.Run(t, new(OptimizedSystemTestSuite))
|
||||
}
|
||||
|
||||
// BenchmarkLockManager 锁管理器基准测试
|
||||
func BenchmarkLockManager(b *testing.B) {
|
||||
// 这里可以添加基准测试
|
||||
b.Skip("基准测试需要Redis连接")
|
||||
}
|
||||
|
||||
// BenchmarkTransactionManager 事务管理器基准测试
|
||||
func BenchmarkTransactionManager(b *testing.B) {
|
||||
// 这里可以添加基准测试
|
||||
b.Skip("基准测试需要数据库连接")
|
||||
}
|
||||
575
services/binanceservice/lock_manager_optimized.go
Normal file
575
services/binanceservice/lock_manager_optimized.go
Normal file
@ -0,0 +1,575 @@
|
||||
package binanceservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// LockManagerConfig 锁管理器专用配置
|
||||
type LockManagerConfig struct {
|
||||
Type LockType `json:"type"`
|
||||
Scope LockScope `json:"scope"`
|
||||
Key string `json:"key"`
|
||||
Expiration time.Duration `json:"expiration"`
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
RetryDelay time.Duration `json:"retry_delay"`
|
||||
}
|
||||
|
||||
// LockType 锁类型
|
||||
type LockType string
|
||||
|
||||
const (
|
||||
LockTypeLocal LockType = "local"
|
||||
LockTypeDistributed LockType = "distributed"
|
||||
)
|
||||
|
||||
// LockScope 锁范围
|
||||
type LockScope string
|
||||
|
||||
const (
|
||||
ScopeOrder LockScope = "order"
|
||||
ScopePosition LockScope = "position"
|
||||
ScopeUser LockScope = "user"
|
||||
ScopeSymbol LockScope = "symbol"
|
||||
ScopeGlobal LockScope = "global"
|
||||
)
|
||||
|
||||
// Lock 锁接口
|
||||
type Lock interface {
|
||||
Lock(ctx context.Context) error
|
||||
Unlock(ctx context.Context) error
|
||||
TryLock(ctx context.Context) (bool, error)
|
||||
IsLocked() bool
|
||||
GetKey() string
|
||||
GetExpiration() time.Duration
|
||||
}
|
||||
|
||||
// LocalLock 本地锁
|
||||
type LocalLock struct {
|
||||
key string
|
||||
mu *sync.RWMutex
|
||||
locked bool
|
||||
expiration time.Duration
|
||||
acquiredAt time.Time
|
||||
}
|
||||
|
||||
// DistributedLock 分布式锁
|
||||
type DistributedLock struct {
|
||||
key string
|
||||
value string
|
||||
expiration time.Duration
|
||||
redisClient *redis.Client
|
||||
locked bool
|
||||
acquiredAt time.Time
|
||||
refreshTicker *time.Ticker
|
||||
stopRefresh chan struct{}
|
||||
}
|
||||
|
||||
// LockManager 锁管理器
|
||||
type LockManager struct {
|
||||
config *OptimizedConfig
|
||||
redisClient *redis.Client
|
||||
localLocks map[string]*LocalLock
|
||||
localMutex sync.RWMutex
|
||||
metrics *MetricsCollector
|
||||
}
|
||||
|
||||
// LockConfig 已在 config_optimized.go 中定义
|
||||
|
||||
// NewLockManager 创建锁管理器
|
||||
func NewLockManager(config *OptimizedConfig, redisClient *redis.Client, metrics *MetricsCollector) *LockManager {
|
||||
return &LockManager{
|
||||
config: config,
|
||||
redisClient: redisClient,
|
||||
localLocks: make(map[string]*LocalLock),
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateLock 创建锁
|
||||
func (lm *LockManager) CreateLock(config LockManagerConfig) Lock {
|
||||
switch config.Type {
|
||||
case LockTypeDistributed:
|
||||
return lm.createDistributedLock(config)
|
||||
case LockTypeLocal:
|
||||
return lm.createLocalLock(config)
|
||||
default:
|
||||
// 默认使用分布式锁
|
||||
return lm.createDistributedLock(config)
|
||||
}
|
||||
}
|
||||
|
||||
// createLocalLock 创建本地锁
|
||||
func (lm *LockManager) createLocalLock(config LockManagerConfig) *LocalLock {
|
||||
lm.localMutex.Lock()
|
||||
defer lm.localMutex.Unlock()
|
||||
|
||||
key := lm.buildLockKey(config.Scope, config.Key)
|
||||
if lock, exists := lm.localLocks[key]; exists {
|
||||
return lock
|
||||
}
|
||||
|
||||
lock := &LocalLock{
|
||||
key: key,
|
||||
mu: &sync.RWMutex{},
|
||||
expiration: config.Expiration,
|
||||
}
|
||||
|
||||
lm.localLocks[key] = lock
|
||||
return lock
|
||||
}
|
||||
|
||||
// createDistributedLock 创建分布式锁
|
||||
func (lm *LockManager) createDistributedLock(config LockManagerConfig) *DistributedLock {
|
||||
key := lm.buildLockKey(config.Scope, config.Key)
|
||||
value := fmt.Sprintf("%d_%s", time.Now().UnixNano(), generateRandomString(8))
|
||||
|
||||
return &DistributedLock{
|
||||
key: key,
|
||||
value: value,
|
||||
expiration: config.Expiration,
|
||||
redisClient: lm.redisClient,
|
||||
stopRefresh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// buildLockKey 构建锁键名
|
||||
func (lm *LockManager) buildLockKey(scope LockScope, key string) string {
|
||||
return fmt.Sprintf("lock:%s:%s", scope, key)
|
||||
}
|
||||
|
||||
// AcquireLock 获取锁(带超时和重试)
|
||||
func (lm *LockManager) AcquireLock(ctx context.Context, config LockManagerConfig) (Lock, error) {
|
||||
lock := lm.CreateLock(config)
|
||||
startTime := time.Now()
|
||||
|
||||
// 设置超时上下文
|
||||
lockCtx := ctx
|
||||
if config.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
lockCtx, cancel = context.WithTimeout(ctx, config.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
// 重试获取锁
|
||||
for {
|
||||
select {
|
||||
case <-lockCtx.Done():
|
||||
waitTime := time.Since(startTime)
|
||||
if lm.metrics != nil {
|
||||
lm.metrics.RecordLockOperation(false, waitTime)
|
||||
}
|
||||
return nil, fmt.Errorf("获取锁超时: %s", config.Key)
|
||||
|
||||
default:
|
||||
err := lock.Lock(lockCtx)
|
||||
if err == nil {
|
||||
waitTime := time.Since(startTime)
|
||||
if lm.metrics != nil {
|
||||
lm.metrics.RecordLockOperation(true, waitTime)
|
||||
}
|
||||
return lock, nil
|
||||
}
|
||||
|
||||
// 等待重试
|
||||
select {
|
||||
case <-lockCtx.Done():
|
||||
return nil, lockCtx.Err()
|
||||
case <-time.After(config.RetryDelay):
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReleaseLock 释放锁
|
||||
func (lm *LockManager) ReleaseLock(ctx context.Context, lock Lock) error {
|
||||
if lock == nil {
|
||||
return nil
|
||||
}
|
||||
return lock.Unlock(ctx)
|
||||
}
|
||||
|
||||
// WithLock 使用锁执行操作
|
||||
func (lm *LockManager) WithLock(ctx context.Context, config LockManagerConfig, operation func() error) error {
|
||||
lock, err := lm.AcquireLock(ctx, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取锁失败: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if unlockErr := lm.ReleaseLock(ctx, lock); unlockErr != nil {
|
||||
logger.Errorf("释放锁失败 [%s]: %v", config.Key, unlockErr)
|
||||
}
|
||||
}()
|
||||
|
||||
return operation()
|
||||
}
|
||||
|
||||
// CleanupExpiredLocks 清理过期的本地锁
|
||||
func (lm *LockManager) CleanupExpiredLocks() {
|
||||
lm.localMutex.Lock()
|
||||
defer lm.localMutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for key, lock := range lm.localLocks {
|
||||
if lock.locked && lock.expiration > 0 && now.Sub(lock.acquiredAt) > lock.expiration {
|
||||
lock.locked = false
|
||||
logger.Warnf("本地锁已过期并被清理: %s", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetLockStatus 获取锁状态
|
||||
func (lm *LockManager) GetLockStatus() map[string]interface{} {
|
||||
lm.localMutex.RLock()
|
||||
defer lm.localMutex.RUnlock()
|
||||
|
||||
status := make(map[string]interface{})
|
||||
localLocks := make(map[string]interface{})
|
||||
|
||||
for key, lock := range lm.localLocks {
|
||||
localLocks[key] = map[string]interface{}{
|
||||
"locked": lock.locked,
|
||||
"acquired_at": lock.acquiredAt,
|
||||
"expiration": lock.expiration,
|
||||
}
|
||||
}
|
||||
|
||||
status["local_locks"] = localLocks
|
||||
status["total_local_locks"] = len(lm.localLocks)
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// LocalLock 实现
|
||||
|
||||
// Lock 获取本地锁
|
||||
func (ll *LocalLock) Lock(ctx context.Context) error {
|
||||
ll.mu.Lock()
|
||||
defer ll.mu.Unlock()
|
||||
|
||||
if ll.locked {
|
||||
return fmt.Errorf("锁已被占用: %s", ll.key)
|
||||
}
|
||||
|
||||
ll.locked = true
|
||||
ll.acquiredAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock 释放本地锁
|
||||
func (ll *LocalLock) Unlock(ctx context.Context) error {
|
||||
ll.mu.Lock()
|
||||
defer ll.mu.Unlock()
|
||||
|
||||
if !ll.locked {
|
||||
return fmt.Errorf("锁未被占用: %s", ll.key)
|
||||
}
|
||||
|
||||
ll.locked = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLock 尝试获取本地锁
|
||||
func (ll *LocalLock) TryLock(ctx context.Context) (bool, error) {
|
||||
ll.mu.Lock()
|
||||
defer ll.mu.Unlock()
|
||||
|
||||
if ll.locked {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ll.locked = true
|
||||
ll.acquiredAt = time.Now()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// IsLocked 检查本地锁是否被占用
|
||||
func (ll *LocalLock) IsLocked() bool {
|
||||
ll.mu.RLock()
|
||||
defer ll.mu.RUnlock()
|
||||
return ll.locked
|
||||
}
|
||||
|
||||
// GetKey 获取锁键名
|
||||
func (ll *LocalLock) GetKey() string {
|
||||
return ll.key
|
||||
}
|
||||
|
||||
// GetExpiration 获取过期时间
|
||||
func (ll *LocalLock) GetExpiration() time.Duration {
|
||||
return ll.expiration
|
||||
}
|
||||
|
||||
// DistributedLock 实现
|
||||
|
||||
// Lock 获取分布式锁
|
||||
func (dl *DistributedLock) Lock(ctx context.Context) error {
|
||||
// 使用 SET key value EX seconds NX 命令
|
||||
result := dl.redisClient.SetNX(ctx, dl.key, dl.value, dl.expiration)
|
||||
if result.Err() != nil {
|
||||
return fmt.Errorf("获取分布式锁失败: %w", result.Err())
|
||||
}
|
||||
|
||||
if !result.Val() {
|
||||
return fmt.Errorf("分布式锁已被占用: %s", dl.key)
|
||||
}
|
||||
|
||||
dl.locked = true
|
||||
dl.acquiredAt = time.Now()
|
||||
|
||||
// 启动锁续期
|
||||
dl.startRefresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock 释放分布式锁
|
||||
func (dl *DistributedLock) Unlock(ctx context.Context) error {
|
||||
if !dl.locked {
|
||||
return fmt.Errorf("分布式锁未被占用: %s", dl.key)
|
||||
}
|
||||
|
||||
// 停止续期
|
||||
dl.stopRefreshProcess()
|
||||
|
||||
// 使用 Lua 脚本确保只删除自己的锁
|
||||
luaScript := `
|
||||
if redis.call("get", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("del", KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
`
|
||||
|
||||
result := dl.redisClient.Eval(ctx, luaScript, []string{dl.key}, dl.value)
|
||||
if result.Err() != nil {
|
||||
return fmt.Errorf("释放分布式锁失败: %w", result.Err())
|
||||
}
|
||||
|
||||
dl.locked = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// TryLock 尝试获取分布式锁
|
||||
func (dl *DistributedLock) TryLock(ctx context.Context) (bool, error) {
|
||||
result := dl.redisClient.SetNX(ctx, dl.key, dl.value, dl.expiration)
|
||||
if result.Err() != nil {
|
||||
return false, fmt.Errorf("尝试获取分布式锁失败: %w", result.Err())
|
||||
}
|
||||
|
||||
if result.Val() {
|
||||
dl.locked = true
|
||||
dl.acquiredAt = time.Now()
|
||||
dl.startRefresh()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// IsLocked 检查分布式锁是否被占用
|
||||
func (dl *DistributedLock) IsLocked() bool {
|
||||
return dl.locked
|
||||
}
|
||||
|
||||
// GetKey 获取锁键名
|
||||
func (dl *DistributedLock) GetKey() string {
|
||||
return dl.key
|
||||
}
|
||||
|
||||
// GetExpiration 获取过期时间
|
||||
func (dl *DistributedLock) GetExpiration() time.Duration {
|
||||
return dl.expiration
|
||||
}
|
||||
|
||||
// startRefresh 启动锁续期
|
||||
func (dl *DistributedLock) startRefresh() {
|
||||
if dl.expiration <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 每隔过期时间的1/3进行续期
|
||||
refreshInterval := dl.expiration / 3
|
||||
dl.refreshTicker = time.NewTicker(refreshInterval)
|
||||
|
||||
go func() {
|
||||
defer dl.refreshTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-dl.stopRefresh:
|
||||
return
|
||||
case <-dl.refreshTicker.C:
|
||||
if !dl.locked {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 Lua 脚本续期
|
||||
luaScript := `
|
||||
if redis.call("get", KEYS[1]) == ARGV[1] then
|
||||
return redis.call("expire", KEYS[1], ARGV[2])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
`
|
||||
|
||||
ctx := context.Background()
|
||||
result := dl.redisClient.Eval(ctx, luaScript, []string{dl.key}, dl.value, int(dl.expiration.Seconds()))
|
||||
if result.Err() != nil {
|
||||
logger.Errorf("分布式锁续期失败 [%s]: %v", dl.key, result.Err())
|
||||
return
|
||||
}
|
||||
|
||||
if result.Val().(int64) == 0 {
|
||||
logger.Warnf("分布式锁已被其他进程占用,停止续期 [%s]", dl.key)
|
||||
dl.locked = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// stopRefreshProcess 停止续期进程
|
||||
func (dl *DistributedLock) stopRefreshProcess() {
|
||||
if dl.refreshTicker != nil {
|
||||
close(dl.stopRefresh)
|
||||
dl.refreshTicker.Stop()
|
||||
dl.refreshTicker = nil
|
||||
dl.stopRefresh = make(chan struct{})
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
|
||||
// generateRandomString 生成随机字符串
|
||||
func generateRandomString(length int) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// 预定义的锁配置
|
||||
|
||||
// GetOrderLockConfig 获取订单锁配置
|
||||
func GetOrderLockConfig(orderID string) LockManagerConfig {
|
||||
return LockManagerConfig{
|
||||
Type: LockTypeDistributed,
|
||||
Scope: ScopeOrder,
|
||||
Key: orderID,
|
||||
Expiration: 30 * time.Second,
|
||||
Timeout: 5 * time.Second,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPositionLockConfig 获取持仓锁配置
|
||||
func GetPositionLockConfig(userID, symbol string) LockManagerConfig {
|
||||
return LockManagerConfig{
|
||||
Type: LockTypeDistributed,
|
||||
Scope: ScopePosition,
|
||||
Key: fmt.Sprintf("%s_%s", userID, symbol),
|
||||
Expiration: 60 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
RetryDelay: 200 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserLockConfig 获取用户锁配置
|
||||
func GetUserLockConfig(userID string) LockManagerConfig {
|
||||
return LockManagerConfig{
|
||||
Type: LockTypeLocal,
|
||||
Scope: ScopeUser,
|
||||
Key: userID,
|
||||
Expiration: 30 * time.Second,
|
||||
Timeout: 3 * time.Second,
|
||||
RetryDelay: 50 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSymbolLockConfig 获取交易对锁配置
|
||||
func GetSymbolLockConfig(symbol string) LockManagerConfig {
|
||||
return LockManagerConfig{
|
||||
Type: LockTypeLocal,
|
||||
Scope: ScopeSymbol,
|
||||
Key: symbol,
|
||||
Expiration: 15 * time.Second,
|
||||
Timeout: 2 * time.Second,
|
||||
RetryDelay: 25 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalLockConfig 获取全局锁配置
|
||||
func GetGlobalLockConfig(operation string) LockManagerConfig {
|
||||
return LockManagerConfig{
|
||||
Type: LockTypeDistributed,
|
||||
Scope: ScopeGlobal,
|
||||
Key: operation,
|
||||
Expiration: 120 * time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
RetryDelay: 500 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
// 全局锁管理器实例
|
||||
var GlobalLockManager *LockManager
|
||||
|
||||
// InitLockManager 初始化锁管理器
|
||||
func InitLockManager(config *OptimizedConfig, redisClient *redis.Client, metrics *MetricsCollector) {
|
||||
GlobalLockManager = NewLockManager(config, redisClient, metrics)
|
||||
|
||||
// 启动清理过期锁的定时任务
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
GlobalLockManager.CleanupExpiredLocks()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// GetLockManager 获取全局锁管理器
|
||||
func GetLockManager() *LockManager {
|
||||
return GlobalLockManager
|
||||
}
|
||||
|
||||
// WithOrderLock 使用订单锁执行操作
|
||||
func WithOrderLock(ctx context.Context, orderID string, operation func() error) error {
|
||||
if GlobalLockManager == nil {
|
||||
return operation()
|
||||
}
|
||||
|
||||
config := GetOrderLockConfig(orderID)
|
||||
return GlobalLockManager.WithLock(ctx, config, operation)
|
||||
}
|
||||
|
||||
// WithPositionLock 使用持仓锁执行操作
|
||||
func WithPositionLock(ctx context.Context, userID, symbol string, operation func() error) error {
|
||||
if GlobalLockManager == nil {
|
||||
return operation()
|
||||
}
|
||||
|
||||
config := GetPositionLockConfig(userID, symbol)
|
||||
return GlobalLockManager.WithLock(ctx, config, operation)
|
||||
}
|
||||
|
||||
// WithUserLock 使用用户锁执行操作
|
||||
func WithUserLock(ctx context.Context, userID string, operation func() error) error {
|
||||
if GlobalLockManager == nil {
|
||||
return operation()
|
||||
}
|
||||
|
||||
config := GetUserLockConfig(userID)
|
||||
return GlobalLockManager.WithLock(ctx, config, operation)
|
||||
}
|
||||
594
services/binanceservice/monitor_optimized.go
Normal file
594
services/binanceservice/monitor_optimized.go
Normal file
@ -0,0 +1,594 @@
|
||||
package binanceservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/logger"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// MetricsCollector 指标收集器
|
||||
type MetricsCollector struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
// 业务指标
|
||||
OrderMetrics *OrderMetrics
|
||||
PositionMetrics *PositionMetrics
|
||||
TakeProfitMetrics *TakeProfitMetrics
|
||||
|
||||
// 技术指标
|
||||
PerformanceMetrics *PerformanceMetrics
|
||||
ErrorMetrics *ErrorMetrics
|
||||
|
||||
// 系统指标
|
||||
SystemMetrics *SystemMetrics
|
||||
|
||||
// 配置
|
||||
config *OptimizedConfig
|
||||
|
||||
// 控制
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// OrderMetrics 订单相关指标
|
||||
type OrderMetrics struct {
|
||||
// 订单处理统计
|
||||
TotalProcessed int64 `json:"total_processed"`
|
||||
SuccessfulOrders int64 `json:"successful_orders"`
|
||||
FailedOrders int64 `json:"failed_orders"`
|
||||
CanceledOrders int64 `json:"canceled_orders"`
|
||||
|
||||
// 反向订单统计
|
||||
ReverseOrdersCreated int64 `json:"reverse_orders_created"`
|
||||
ReverseOrdersFailed int64 `json:"reverse_orders_failed"`
|
||||
|
||||
// 响应时间统计
|
||||
TotalResponseTime time.Duration `json:"total_response_time"`
|
||||
MaxResponseTime time.Duration `json:"max_response_time"`
|
||||
MinResponseTime time.Duration `json:"min_response_time"`
|
||||
|
||||
// 最近更新时间
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// PositionMetrics 持仓相关指标
|
||||
type PositionMetrics struct {
|
||||
// 持仓同步统计
|
||||
SyncAttempts int64 `json:"sync_attempts"`
|
||||
SyncSuccesses int64 `json:"sync_successes"`
|
||||
SyncFailures int64 `json:"sync_failures"`
|
||||
|
||||
// 持仓差异统计
|
||||
PositionMismatches int64 `json:"position_mismatches"`
|
||||
MaxDifference decimal.Decimal `json:"max_difference"`
|
||||
TotalDifference decimal.Decimal `json:"total_difference"`
|
||||
|
||||
// 平仓统计
|
||||
ClosePositions int64 `json:"close_positions"`
|
||||
ForceClosePositions int64 `json:"force_close_positions"`
|
||||
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// TakeProfitMetrics 止盈止损相关指标
|
||||
type TakeProfitMetrics struct {
|
||||
// 止盈止损创建统计
|
||||
TakeProfitCreated int64 `json:"take_profit_created"`
|
||||
StopLossCreated int64 `json:"stop_loss_created"`
|
||||
|
||||
// 止盈止损执行统计
|
||||
TakeProfitExecuted int64 `json:"take_profit_executed"`
|
||||
StopLossExecuted int64 `json:"stop_loss_executed"`
|
||||
|
||||
// 取消统计
|
||||
OrdersCanceled int64 `json:"orders_canceled"`
|
||||
BatchCancelAttempts int64 `json:"batch_cancel_attempts"`
|
||||
BatchCancelSuccesses int64 `json:"batch_cancel_successes"`
|
||||
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// PerformanceMetrics 性能相关指标
|
||||
type PerformanceMetrics struct {
|
||||
// 并发统计
|
||||
ConcurrentRequests int64 `json:"concurrent_requests"`
|
||||
MaxConcurrency int64 `json:"max_concurrency"`
|
||||
|
||||
// 锁统计
|
||||
LockAcquisitions int64 `json:"lock_acquisitions"`
|
||||
LockWaitTime time.Duration `json:"lock_wait_time"`
|
||||
LockTimeouts int64 `json:"lock_timeouts"`
|
||||
|
||||
// 数据库操作统计
|
||||
DbQueries int64 `json:"db_queries"`
|
||||
DbTransactions int64 `json:"db_transactions"`
|
||||
DbQueryTime time.Duration `json:"db_query_time"`
|
||||
DbErrors int64 `json:"db_errors"`
|
||||
|
||||
// API调用统计
|
||||
ApiCalls int64 `json:"api_calls"`
|
||||
ApiCallTime time.Duration `json:"api_call_time"`
|
||||
ApiErrors int64 `json:"api_errors"`
|
||||
ApiRetries int64 `json:"api_retries"`
|
||||
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// ErrorMetrics 错误相关指标
|
||||
type ErrorMetrics struct {
|
||||
// 错误分类统计
|
||||
NetworkErrors int64 `json:"network_errors"`
|
||||
DatabaseErrors int64 `json:"database_errors"`
|
||||
BusinessErrors int64 `json:"business_errors"`
|
||||
SystemErrors int64 `json:"system_errors"`
|
||||
|
||||
// 重试统计
|
||||
RetryAttempts int64 `json:"retry_attempts"`
|
||||
RetrySuccesses int64 `json:"retry_successes"`
|
||||
RetryFailures int64 `json:"retry_failures"`
|
||||
|
||||
// 最近错误
|
||||
RecentErrors []ErrorRecord `json:"recent_errors"`
|
||||
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// SystemMetrics 系统相关指标
|
||||
type SystemMetrics struct {
|
||||
// 内存使用
|
||||
MemoryUsage uint64 `json:"memory_usage"`
|
||||
MemoryPercent float64 `json:"memory_percent"`
|
||||
|
||||
// CPU使用
|
||||
CpuPercent float64 `json:"cpu_percent"`
|
||||
|
||||
// Goroutine统计
|
||||
GoroutineCount int `json:"goroutine_count"`
|
||||
|
||||
// GC统计
|
||||
GcCount uint32 `json:"gc_count"`
|
||||
GcPauseMs uint64 `json:"gc_pause_ms"`
|
||||
|
||||
LastUpdated time.Time `json:"last_updated"`
|
||||
}
|
||||
|
||||
// ErrorRecord 错误记录
|
||||
type ErrorRecord struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
Context string `json:"context"`
|
||||
}
|
||||
|
||||
// NewMetricsCollector 创建新的指标收集器
|
||||
func NewMetricsCollector(config *OptimizedConfig) *MetricsCollector {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &MetricsCollector{
|
||||
OrderMetrics: &OrderMetrics{
|
||||
MinResponseTime: time.Hour, // 初始化为一个大值
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
PositionMetrics: &PositionMetrics{
|
||||
MaxDifference: decimal.Zero,
|
||||
TotalDifference: decimal.Zero,
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
TakeProfitMetrics: &TakeProfitMetrics{
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
PerformanceMetrics: &PerformanceMetrics{
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
ErrorMetrics: &ErrorMetrics{
|
||||
RecentErrors: make([]ErrorRecord, 0, 100),
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
SystemMetrics: &SystemMetrics{
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
config: config,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
startTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// Start 启动指标收集
|
||||
func (mc *MetricsCollector) Start() {
|
||||
if !mc.config.MonitorConfig.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("启动指标收集器")
|
||||
|
||||
// 启动定期收集
|
||||
go mc.collectLoop()
|
||||
|
||||
// 启动告警检查
|
||||
go mc.alertLoop()
|
||||
}
|
||||
|
||||
// Stop 停止指标收集
|
||||
func (mc *MetricsCollector) Stop() {
|
||||
logger.Info("停止指标收集器")
|
||||
mc.cancel()
|
||||
}
|
||||
|
||||
// collectLoop 收集循环
|
||||
func (mc *MetricsCollector) collectLoop() {
|
||||
ticker := time.NewTicker(mc.config.MonitorConfig.CollectInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mc.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
mc.collectSystemMetrics()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alertLoop 告警检查循环
|
||||
func (mc *MetricsCollector) alertLoop() {
|
||||
ticker := time.NewTicker(time.Minute) // 每分钟检查一次
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mc.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
mc.checkAlerts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RecordOrderProcessed 记录订单处理
|
||||
func (mc *MetricsCollector) RecordOrderProcessed(success bool, responseTime time.Duration) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
atomic.AddInt64(&mc.OrderMetrics.TotalProcessed, 1)
|
||||
|
||||
if success {
|
||||
atomic.AddInt64(&mc.OrderMetrics.SuccessfulOrders, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&mc.OrderMetrics.FailedOrders, 1)
|
||||
}
|
||||
|
||||
// 更新响应时间统计
|
||||
mc.OrderMetrics.TotalResponseTime += responseTime
|
||||
if responseTime > mc.OrderMetrics.MaxResponseTime {
|
||||
mc.OrderMetrics.MaxResponseTime = responseTime
|
||||
}
|
||||
if responseTime < mc.OrderMetrics.MinResponseTime {
|
||||
mc.OrderMetrics.MinResponseTime = responseTime
|
||||
}
|
||||
|
||||
mc.OrderMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordOrderCanceled 记录订单取消
|
||||
func (mc *MetricsCollector) RecordOrderCanceled() {
|
||||
atomic.AddInt64(&mc.OrderMetrics.CanceledOrders, 1)
|
||||
mc.OrderMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordReverseOrder 记录反向订单
|
||||
func (mc *MetricsCollector) RecordReverseOrder(success bool) {
|
||||
if success {
|
||||
atomic.AddInt64(&mc.OrderMetrics.ReverseOrdersCreated, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&mc.OrderMetrics.ReverseOrdersFailed, 1)
|
||||
}
|
||||
mc.OrderMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordPositionSync 记录持仓同步
|
||||
func (mc *MetricsCollector) RecordPositionSync(success bool, difference decimal.Decimal) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
atomic.AddInt64(&mc.PositionMetrics.SyncAttempts, 1)
|
||||
|
||||
if success {
|
||||
atomic.AddInt64(&mc.PositionMetrics.SyncSuccesses, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&mc.PositionMetrics.SyncFailures, 1)
|
||||
}
|
||||
|
||||
// 更新差异统计
|
||||
if !difference.IsZero() {
|
||||
atomic.AddInt64(&mc.PositionMetrics.PositionMismatches, 1)
|
||||
mc.PositionMetrics.TotalDifference = mc.PositionMetrics.TotalDifference.Add(difference.Abs())
|
||||
|
||||
if difference.Abs().GreaterThan(mc.PositionMetrics.MaxDifference) {
|
||||
mc.PositionMetrics.MaxDifference = difference.Abs()
|
||||
}
|
||||
}
|
||||
|
||||
mc.PositionMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordClosePosition 记录平仓
|
||||
func (mc *MetricsCollector) RecordClosePosition(forced bool) {
|
||||
atomic.AddInt64(&mc.PositionMetrics.ClosePositions, 1)
|
||||
if forced {
|
||||
atomic.AddInt64(&mc.PositionMetrics.ForceClosePositions, 1)
|
||||
}
|
||||
mc.PositionMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordTakeProfitCreated 记录止盈止损创建
|
||||
func (mc *MetricsCollector) RecordTakeProfitCreated(orderType string) {
|
||||
switch orderType {
|
||||
case "TAKE_PROFIT", "TAKE_PROFIT_MARKET":
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.TakeProfitCreated, 1)
|
||||
case "STOP", "STOP_MARKET":
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.StopLossCreated, 1)
|
||||
}
|
||||
mc.TakeProfitMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordTakeProfitExecuted 记录止盈止损执行
|
||||
func (mc *MetricsCollector) RecordTakeProfitExecuted(orderType string) {
|
||||
switch orderType {
|
||||
case "TAKE_PROFIT", "TAKE_PROFIT_MARKET":
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.TakeProfitExecuted, 1)
|
||||
case "STOP", "STOP_MARKET":
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.StopLossExecuted, 1)
|
||||
}
|
||||
mc.TakeProfitMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordOrdersCanceled 记录订单取消
|
||||
func (mc *MetricsCollector) RecordOrdersCanceled(count int) {
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.OrdersCanceled, int64(count))
|
||||
mc.TakeProfitMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordBatchCancel 记录批量取消
|
||||
func (mc *MetricsCollector) RecordBatchCancel(success bool) {
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.BatchCancelAttempts, 1)
|
||||
if success {
|
||||
atomic.AddInt64(&mc.TakeProfitMetrics.BatchCancelSuccesses, 1)
|
||||
}
|
||||
mc.TakeProfitMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordLockOperation 记录锁操作
|
||||
func (mc *MetricsCollector) RecordLockOperation(acquired bool, waitTime time.Duration) {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.LockAcquisitions, 1)
|
||||
mc.PerformanceMetrics.LockWaitTime += waitTime
|
||||
|
||||
if !acquired {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.LockTimeouts, 1)
|
||||
}
|
||||
|
||||
mc.PerformanceMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordDbOperation 记录数据库操作
|
||||
func (mc *MetricsCollector) RecordDbOperation(isTransaction bool, duration time.Duration, err error) {
|
||||
if isTransaction {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.DbTransactions, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.DbQueries, 1)
|
||||
}
|
||||
|
||||
mc.PerformanceMetrics.DbQueryTime += duration
|
||||
|
||||
if err != nil {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.DbErrors, 1)
|
||||
mc.RecordError("database", err.Error(), "db_operation")
|
||||
}
|
||||
|
||||
mc.PerformanceMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordApiCall 记录API调用
|
||||
func (mc *MetricsCollector) RecordApiCall(duration time.Duration, err error, retryCount int) {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.ApiCalls, 1)
|
||||
mc.PerformanceMetrics.ApiCallTime += duration
|
||||
|
||||
if err != nil {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.ApiErrors, 1)
|
||||
mc.RecordError("api", err.Error(), "api_call")
|
||||
}
|
||||
|
||||
if retryCount > 0 {
|
||||
atomic.AddInt64(&mc.PerformanceMetrics.ApiRetries, int64(retryCount))
|
||||
}
|
||||
|
||||
mc.PerformanceMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordConcurrency 记录并发数
|
||||
func (mc *MetricsCollector) RecordConcurrency(current int64) {
|
||||
atomic.StoreInt64(&mc.PerformanceMetrics.ConcurrentRequests, current)
|
||||
|
||||
if current > atomic.LoadInt64(&mc.PerformanceMetrics.MaxConcurrency) {
|
||||
atomic.StoreInt64(&mc.PerformanceMetrics.MaxConcurrency, current)
|
||||
}
|
||||
|
||||
mc.PerformanceMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordError 记录错误
|
||||
func (mc *MetricsCollector) RecordError(errorType, message, context string) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
// 分类统计
|
||||
switch errorType {
|
||||
case "network":
|
||||
atomic.AddInt64(&mc.ErrorMetrics.NetworkErrors, 1)
|
||||
case "database":
|
||||
atomic.AddInt64(&mc.ErrorMetrics.DatabaseErrors, 1)
|
||||
case "business":
|
||||
atomic.AddInt64(&mc.ErrorMetrics.BusinessErrors, 1)
|
||||
default:
|
||||
atomic.AddInt64(&mc.ErrorMetrics.SystemErrors, 1)
|
||||
}
|
||||
|
||||
// 记录最近错误
|
||||
errorRecord := ErrorRecord{
|
||||
Timestamp: time.Now(),
|
||||
Type: errorType,
|
||||
Message: message,
|
||||
Context: context,
|
||||
}
|
||||
|
||||
// 保持最近100个错误
|
||||
if len(mc.ErrorMetrics.RecentErrors) >= 100 {
|
||||
mc.ErrorMetrics.RecentErrors = mc.ErrorMetrics.RecentErrors[1:]
|
||||
}
|
||||
mc.ErrorMetrics.RecentErrors = append(mc.ErrorMetrics.RecentErrors, errorRecord)
|
||||
|
||||
mc.ErrorMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// RecordRetry 记录重试
|
||||
func (mc *MetricsCollector) RecordRetry(success bool) {
|
||||
atomic.AddInt64(&mc.ErrorMetrics.RetryAttempts, 1)
|
||||
if success {
|
||||
atomic.AddInt64(&mc.ErrorMetrics.RetrySuccesses, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&mc.ErrorMetrics.RetryFailures, 1)
|
||||
}
|
||||
mc.ErrorMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// collectSystemMetrics 收集系统指标
|
||||
func (mc *MetricsCollector) collectSystemMetrics() {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
// 获取内存统计
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
mc.SystemMetrics.MemoryUsage = m.Alloc
|
||||
mc.SystemMetrics.GoroutineCount = runtime.NumGoroutine()
|
||||
mc.SystemMetrics.GcCount = m.NumGC
|
||||
mc.SystemMetrics.GcPauseMs = uint64(m.PauseNs[(m.NumGC+255)%256]) / 1000000
|
||||
|
||||
mc.SystemMetrics.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// checkAlerts 检查告警
|
||||
func (mc *MetricsCollector) checkAlerts() {
|
||||
thresholds := mc.config.MonitorConfig.AlertThresholds
|
||||
|
||||
// 检查订单失败率
|
||||
if mc.OrderMetrics.TotalProcessed > 0 {
|
||||
failureRate := float64(mc.OrderMetrics.FailedOrders) / float64(mc.OrderMetrics.TotalProcessed) * 100
|
||||
if failureRate > thresholds.OrderFailureRate {
|
||||
logger.Warnf("订单失败率告警: %.2f%% (阈值: %.2f%%)", failureRate, thresholds.OrderFailureRate)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查持仓同步失败率
|
||||
if mc.PositionMetrics.SyncAttempts > 0 {
|
||||
syncFailureRate := float64(mc.PositionMetrics.SyncFailures) / float64(mc.PositionMetrics.SyncAttempts) * 100
|
||||
if syncFailureRate > thresholds.PositionSyncFailureRate {
|
||||
logger.Warnf("持仓同步失败率告警: %.2f%% (阈值: %.2f%%)", syncFailureRate, thresholds.PositionSyncFailureRate)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查API失败率
|
||||
if mc.PerformanceMetrics.ApiCalls > 0 {
|
||||
apiFailureRate := float64(mc.PerformanceMetrics.ApiErrors) / float64(mc.PerformanceMetrics.ApiCalls) * 100
|
||||
if apiFailureRate > thresholds.ApiFailureRate {
|
||||
logger.Warnf("API失败率告警: %.2f%% (阈值: %.2f%%)", apiFailureRate, thresholds.ApiFailureRate)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查内存使用率
|
||||
if mc.SystemMetrics.MemoryPercent > thresholds.MemoryUsageThreshold {
|
||||
logger.Warnf("内存使用率告警: %.2f%% (阈值: %.2f%%)", mc.SystemMetrics.MemoryPercent, thresholds.MemoryUsageThreshold)
|
||||
}
|
||||
|
||||
// 检查CPU使用率
|
||||
if mc.SystemMetrics.CpuPercent > thresholds.CpuUsageThreshold {
|
||||
logger.Warnf("CPU使用率告警: %.2f%% (阈值: %.2f%%)", mc.SystemMetrics.CpuPercent, thresholds.CpuUsageThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
// GetMetrics 获取所有指标
|
||||
func (mc *MetricsCollector) GetMetrics() map[string]interface{} {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
return map[string]interface{}{
|
||||
"order_metrics": mc.OrderMetrics,
|
||||
"position_metrics": mc.PositionMetrics,
|
||||
"take_profit_metrics": mc.TakeProfitMetrics,
|
||||
"performance_metrics": mc.PerformanceMetrics,
|
||||
"error_metrics": mc.ErrorMetrics,
|
||||
"system_metrics": mc.SystemMetrics,
|
||||
"uptime": time.Since(mc.startTime).String(),
|
||||
"collected_at": time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetSummary 获取指标摘要
|
||||
func (mc *MetricsCollector) GetSummary() map[string]interface{} {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
summary := make(map[string]interface{})
|
||||
|
||||
// 订单摘要
|
||||
if mc.OrderMetrics.TotalProcessed > 0 {
|
||||
successRate := float64(mc.OrderMetrics.SuccessfulOrders) / float64(mc.OrderMetrics.TotalProcessed) * 100
|
||||
avgResponseTime := mc.OrderMetrics.TotalResponseTime / time.Duration(mc.OrderMetrics.TotalProcessed)
|
||||
|
||||
summary["order_success_rate"] = fmt.Sprintf("%.2f%%", successRate)
|
||||
summary["avg_response_time"] = avgResponseTime.String()
|
||||
summary["total_orders"] = mc.OrderMetrics.TotalProcessed
|
||||
}
|
||||
|
||||
// 持仓摘要
|
||||
if mc.PositionMetrics.SyncAttempts > 0 {
|
||||
syncSuccessRate := float64(mc.PositionMetrics.SyncSuccesses) / float64(mc.PositionMetrics.SyncAttempts) * 100
|
||||
summary["position_sync_rate"] = fmt.Sprintf("%.2f%%", syncSuccessRate)
|
||||
summary["position_mismatches"] = mc.PositionMetrics.PositionMismatches
|
||||
}
|
||||
|
||||
// 性能摘要
|
||||
summary["current_concurrency"] = mc.PerformanceMetrics.ConcurrentRequests
|
||||
summary["max_concurrency"] = mc.PerformanceMetrics.MaxConcurrency
|
||||
summary["total_api_calls"] = mc.PerformanceMetrics.ApiCalls
|
||||
summary["total_db_queries"] = mc.PerformanceMetrics.DbQueries
|
||||
|
||||
// 系统摘要
|
||||
summary["memory_usage_mb"] = mc.SystemMetrics.MemoryUsage / 1024 / 1024
|
||||
summary["goroutine_count"] = mc.SystemMetrics.GoroutineCount
|
||||
summary["uptime"] = time.Since(mc.startTime).String()
|
||||
|
||||
return summary
|
||||
}
|
||||
|
||||
// 全局指标收集器实例
|
||||
var GlobalMetricsCollector *MetricsCollector
|
||||
|
||||
// InitMetricsCollector 初始化指标收集器
|
||||
func InitMetricsCollector(config *OptimizedConfig) {
|
||||
GlobalMetricsCollector = NewMetricsCollector(config)
|
||||
GlobalMetricsCollector.Start()
|
||||
}
|
||||
|
||||
// GetMetricsCollector 获取全局指标收集器
|
||||
func GetMetricsCollector() *MetricsCollector {
|
||||
return GlobalMetricsCollector
|
||||
}
|
||||
@ -170,13 +170,15 @@ func (e *ReverseService) ReverseOrder(apiKey string, mapData map[string]interfac
|
||||
switch {
|
||||
case mainOrder.PositionSide == "LONG" && mainOrder.Side == "BUY", mainOrder.PositionSide == "SHORT" && mainOrder.Side == "SELL":
|
||||
if mainOrder.Category == 0 {
|
||||
if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, false); err1 != nil {
|
||||
if _, _, err1 := e.savePositionWithRetry(&mainOrder, &apiInfo, false, false); err1 != nil {
|
||||
e.Log.Errorf("反向订单持仓保存失败: %v", err1)
|
||||
return true, err1
|
||||
}
|
||||
}
|
||||
case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SrgetELL":
|
||||
case mainOrder.PositionSide == "SHORT" && mainOrder.Side == "BUY", mainOrder.PositionSide == "LONG" && mainOrder.Side == "SELL":
|
||||
if mainOrder.Category == 0 {
|
||||
if _, _, err1 := e.savePosition(&mainOrder, &apiInfo, false, true); err1 != nil {
|
||||
if _, _, err1 := e.savePositionWithRetry(&mainOrder, &apiInfo, false, true); err1 != nil {
|
||||
e.Log.Errorf("反向订单持仓保存失败: %v", err1)
|
||||
return true, err1
|
||||
}
|
||||
}
|
||||
@ -307,6 +309,20 @@ func (e *ReverseService) SaveMainOrder(mapData map[string]interface{}, apiInfo D
|
||||
return reverseOrder, nil
|
||||
}
|
||||
|
||||
// savePositionWithRetry 带重试机制的持仓保存
|
||||
func (e *ReverseService) savePositionWithRetry(order *DbModels.LineReverseOrder, apiInfo *DbModels.LineApiUser, isMain, reducePosition bool) (bool, bool, error) {
|
||||
const maxRetries = 3
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
needReverseOrder, closePosition, err := e.savePosition(order, apiInfo, isMain, reducePosition)
|
||||
if err == nil {
|
||||
return needReverseOrder, closePosition, nil
|
||||
}
|
||||
e.Log.Warnf("持仓保存失败,重试 %d/%d: %v", i+1, maxRetries, err)
|
||||
time.Sleep(time.Millisecond * 100 * time.Duration(i+1))
|
||||
}
|
||||
return false, false, fmt.Errorf("持仓保存失败,已重试 %d 次", maxRetries)
|
||||
}
|
||||
|
||||
// 更新仓位信息
|
||||
// apiInfo: 当前下单api信息
|
||||
// return
|
||||
@ -364,25 +380,27 @@ func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo
|
||||
//平仓
|
||||
if closePosition {
|
||||
totalNum = decimal.Zero
|
||||
sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2 where id =@id and status!=2 "
|
||||
sqlStr = "UPDATE line_reverse_position set amount=@totalNum,updated_at=now(),status=2,version=version+1 where id =@id and status!=2 and version=@version"
|
||||
} else if reducePosition {
|
||||
//只减仓
|
||||
sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice where id =@id and status!=2 "
|
||||
sqlStr = "UPDATE line_reverse_position set amount=amount - @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version"
|
||||
} else {
|
||||
sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice where id =@id and status!=2"
|
||||
sqlStr = "UPDATE line_reverse_position set total_amount=total_amount + @totalNum,amount=amount + @totalNum,updated_at=now(),average_price=@averagePrice,version=version+1 where id =@id and status!=2 and version=@version"
|
||||
}
|
||||
} else {
|
||||
querySql = "reverse_api_id =? and position_side =? and symbol =? and reverse_status in (0,1)"
|
||||
//减仓 判断是否为平仓
|
||||
closePosition = e.getClosePosition(reducePosition, apiInfo, order, order.PositionSide, isMain)
|
||||
|
||||
// 关键修复:确保反向持仓状态正确更新
|
||||
if closePosition {
|
||||
totalNum = decimal.Zero
|
||||
sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2 where id =@id and reverse_status !=2"
|
||||
// 增强的平仓更新逻辑,确保状态正确设置为2
|
||||
sqlStr = "UPDATE line_reverse_position set reverse_amount=@totalNum,updated_at=now(),reverse_status=2,version=version+1 where id =@id and reverse_status !=2 and version=@version"
|
||||
} else if reducePosition {
|
||||
sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice where id =@id and reverse_status !=2"
|
||||
sqlStr = "UPDATE line_reverse_position set reverse_amount=reverse_amount - @totalNum,updated_at=now(),reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version"
|
||||
} else {
|
||||
sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice where id =@id and reverse_status !=2"
|
||||
sqlStr = "UPDATE line_reverse_position set total_reverse_amount=total_reverse_amount + @totalNum,reverse_amount=reverse_amount + @totalNum,updated_at=now(),reverse_status =1,reverse_average_price=@averagePrice,version=version+1 where id =@id and reverse_status !=2 and version=@version"
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,22 +408,23 @@ func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo
|
||||
var remainQuantity decimal.Decimal
|
||||
|
||||
err := e.Orm.Transaction(func(tx *gorm.DB) error {
|
||||
err1 := tx.Model(&position).Where(querySql,
|
||||
order.ApiId, positionSide, order.Symbol).First(&position).Error
|
||||
err1 := tx.Model(&position).Where(querySql,
|
||||
order.ApiId, positionSide, order.Symbol).First(&position).Error
|
||||
|
||||
if err1 != nil {
|
||||
//主单仓位不存在,创建新仓位
|
||||
if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition {
|
||||
if err1 != nil {
|
||||
//主单仓位不存在,创建新仓位
|
||||
if isMain && errors.Is(err1, gorm.ErrRecordNotFound) && !reducePosition {
|
||||
// 初始化版本号
|
||||
position.Version = 0
|
||||
if err2 := tx.Create(&position).Error; err2 != nil {
|
||||
return err2
|
||||
}
|
||||
|
||||
if err2 := tx.Create(&position).Error; err2 != nil {
|
||||
return err2
|
||||
averagePrice = position.AveragePrice
|
||||
} else {
|
||||
return err1
|
||||
}
|
||||
|
||||
averagePrice = position.AveragePrice
|
||||
} else {
|
||||
return err1
|
||||
}
|
||||
} else {
|
||||
var totalAmount decimal.Decimal
|
||||
var totalPrice decimal.Decimal
|
||||
if !position.Amount.IsZero() && !position.AveragePrice.IsZero() {
|
||||
@ -457,16 +476,17 @@ func (e *ReverseService) savePosition(order *DbModels.LineReverseOrder, apiInfo
|
||||
return err2
|
||||
}
|
||||
|
||||
dbResult := tx.Exec(sqlStr, sql.Named("totalNum", totalNum), sql.Named("id", position.Id), sql.Named("averagePrice", averagePrice))
|
||||
if dbResult.Error != nil {
|
||||
return dbResult.Error
|
||||
}
|
||||
// 使用乐观锁执行更新
|
||||
dbResult := tx.Exec(sqlStr, sql.Named("totalNum", totalNum), sql.Named("id", position.Id), sql.Named("averagePrice", averagePrice), sql.Named("version", position.Version))
|
||||
if dbResult.Error != nil {
|
||||
return dbResult.Error
|
||||
}
|
||||
|
||||
order.PositionId = position.Id
|
||||
if dbResult.RowsAffected == 0 {
|
||||
e.Log.Errorf("减仓数据 是否平仓单:%v :%v", closePosition, order)
|
||||
return errors.New("没有找到对应的持仓信息")
|
||||
}
|
||||
order.PositionId = position.Id
|
||||
if dbResult.RowsAffected == 0 {
|
||||
e.Log.Errorf("减仓数据 是否平仓单:%v :%v, 可能存在并发冲突", closePosition, order)
|
||||
return fmt.Errorf("没有找到对应的持仓信息或版本冲突,position_id:%d, version:%d", position.Id, position.Version)
|
||||
}
|
||||
|
||||
if reducePosition && !isMain {
|
||||
remainQuantity = position.ReverseAmount.Sub(totalNum)
|
||||
@ -1052,145 +1072,3 @@ func getOrderType(t string) int {
|
||||
}
|
||||
return 2
|
||||
}
|
||||
|
||||
// // 重下止盈止损
|
||||
// // mapData: 主单止盈止损回调
|
||||
// func (e *ReverseService) ReTakeOrStopOrder(mapData *map[string]interface{}, orderSn string, mainApiInfo *DbModels.LineApiUser, symbol *models.TradeSet) error {
|
||||
// orderType := 0 //订单类型 1-止盈 2-止损
|
||||
// side, err := maphelper.GetString(*mapData, "S")
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// ot, err := maphelper.GetString(*mapData, "ot")
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// //反单止盈止损方向相反
|
||||
// if side == "SELL" {
|
||||
// side = "BUY"
|
||||
// } else {
|
||||
// side = "SELL"
|
||||
// }
|
||||
|
||||
// positionSide, err := maphelper.GetString(*mapData, "ps")
|
||||
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// if positionSide == "LONG" {
|
||||
// positionSide = "SHORT"
|
||||
// } else {
|
||||
// positionSide = "LONG"
|
||||
// }
|
||||
|
||||
// close := maphelper.GetBool(*mapData, "cp")
|
||||
// stopPrice := maphelper.GetDecimal(*mapData, "sp")
|
||||
// if stopPrice.IsZero() {
|
||||
// e.Log.Errorf("获取止盈止损单触发价失败 symbol:%s custom:%s :%v", symbol, orderSn, err)
|
||||
// return err
|
||||
// }
|
||||
// apiInfo, err := GetApiInfo(mainApiInfo.ReverseApiId)
|
||||
|
||||
// if err != nil {
|
||||
// e.Log.Errorf("根据主单api获取反单api失败 symbol:%s custom:%s :%v", symbol, orderSn, err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// switch ot {
|
||||
// case "STOP_MARKET", "STOP":
|
||||
// orderType = 2
|
||||
// case "TAKE_PROFIT_MARKET", "TAKE_PROFIT":
|
||||
// orderType = 1
|
||||
// default:
|
||||
// return fmt.Errorf("不支持的订单类型 ot:%s", ot)
|
||||
// }
|
||||
|
||||
// var reversePosition DbModels.LineReversePosition
|
||||
|
||||
// e.Orm.Model(&reversePosition).
|
||||
// Where("symbol =? and reverse_api_id =? and position_side =? and reverse_status =1", symbol.GetSymbol(), apiInfo.Id, positionSide).
|
||||
// First(&reversePosition)
|
||||
|
||||
// if reversePosition.Id == 0 {
|
||||
// e.Log.Errorf("获取反单持仓失败 symbol:%s custom:%s :%v", symbol, orderSn, err)
|
||||
// return err
|
||||
// }
|
||||
|
||||
// mainPercent := decimal.NewFromInt(1)
|
||||
|
||||
// if !stopPrice.IsZero() && !reversePosition.AveragePrice.IsZero() {
|
||||
// mainPercent = stopPrice.Div(reversePosition.AveragePrice)
|
||||
// mainPercent = (mainPercent.Sub(decimal.NewFromInt(1))).Abs().Truncate(4)
|
||||
// }
|
||||
|
||||
// var percent decimal.Decimal
|
||||
// switch {
|
||||
// //做多止损
|
||||
// case orderType == 2 && positionSide == "LONG", orderType == 1 && positionSide == "SHORT":
|
||||
// percent = decimal.NewFromInt(1).Sub(mainPercent)
|
||||
// case orderType == 2 && positionSide == "SHORT", orderType == 1 && positionSide == "LONG":
|
||||
// percent = decimal.NewFromInt(1).Add(mainPercent)
|
||||
// default:
|
||||
// return fmt.Errorf("不支持的订单类型 ot:%s, ps:%s", ot, positionSide)
|
||||
// }
|
||||
|
||||
// now := time.Now()
|
||||
// price := reversePosition.AveragePrice.Mul(percent).Truncate(int32(symbol.PriceDigit))
|
||||
// lastPrice, _ := decimal.NewFromString(symbol.LastPrice)
|
||||
// newOrder := DbModels.LineReverseOrder{
|
||||
// PositionId: reversePosition.Id,
|
||||
// OrderSn: helper.GetOrderNo(),
|
||||
// OrderType: orderType,
|
||||
// Status: 1,
|
||||
// Price: price,
|
||||
// TotalNum: reversePosition.TotalReverseAmount,
|
||||
// Symbol: symbol.GetSymbol(),
|
||||
// Side: side,
|
||||
// PositionSide: positionSide,
|
||||
// FollowOrderSn: orderSn,
|
||||
// Type: ot,
|
||||
// SignPrice: lastPrice,
|
||||
// Category: 1,
|
||||
// ApiId: apiInfo.Id,
|
||||
// IsAddPosition: 2,
|
||||
// TriggerTime: &now,
|
||||
// BuyPrice: reversePosition.TotalReverseAmount.Mul(price).Truncate(int32(symbol.PriceDigit)),
|
||||
// }
|
||||
|
||||
// if err1 := e.Orm.Create(&newOrder).Error; err1 != nil {
|
||||
// e.Log.Errorf("保存反单止盈止损失败 symbol:%s custom:%s :%v", symbol, orderSn, err1)
|
||||
// return err1
|
||||
// }
|
||||
// params := FutOrderPlace{
|
||||
// ApiId: apiInfo.Id,
|
||||
// Symbol: symbol.GetSymbol(),
|
||||
// PositionSide: newOrder.PositionSide,
|
||||
// Side: newOrder.Side,
|
||||
// OrderType: ot,
|
||||
// Quantity: newOrder.TotalNum,
|
||||
// Price: price,
|
||||
// StopPrice: price,
|
||||
// Profit: price,
|
||||
// NewClientOrderId: newOrder.OrderSn,
|
||||
// ClosePosition: close,
|
||||
// }
|
||||
// futApiV2 := FuturesResetV2{Service: e.Service}
|
||||
// err = futApiV2.OrderPlaceLoop(&apiInfo, params)
|
||||
|
||||
// if err != nil {
|
||||
// e.Log.Errorf("币安下单失败 symbol:%s custom:%s :%v", symbol.GetSymbol(), orderSn, err)
|
||||
|
||||
// if err1 := e.Orm.Model(&newOrder).Updates(map[string]interface{}{"status": 8, "remark": err.Error(), "updated_at": time.Now()}).Error; err1 != nil {
|
||||
// e.Log.Errorf("更新订单状态失败 symbol:%s custom:%s :%v", newOrder.Symbol, newOrder.OrderSn, err1)
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
|
||||
// e.DoCancelTakeProfitBatch(symbol.GetSymbol(), newOrder.PositionSide, newOrder.Side, newOrder.OrderType, &apiInfo)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
1121
services/binanceservice/reverse_service_optimized.go
Normal file
1121
services/binanceservice/reverse_service_optimized.go
Normal file
File diff suppressed because it is too large
Load Diff
457
services/binanceservice/transaction_manager_optimized.go
Normal file
457
services/binanceservice/transaction_manager_optimized.go
Normal file
@ -0,0 +1,457 @@
|
||||
package binanceservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-admin-team/go-admin-core/logger"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TransactionManager 事务管理器
|
||||
type TransactionManager struct {
|
||||
db *gorm.DB
|
||||
metrics *MetricsCollector
|
||||
config *OptimizedConfig
|
||||
}
|
||||
|
||||
// TransactionConfig 事务配置
|
||||
type TransactionConfig struct {
|
||||
Timeout time.Duration `json:"timeout"`
|
||||
RetryAttempts int `json:"retry_attempts"`
|
||||
RetryDelay time.Duration `json:"retry_delay"`
|
||||
IsolationLevel string `json:"isolation_level"`
|
||||
ReadOnly bool `json:"read_only"`
|
||||
}
|
||||
|
||||
// TransactionContext 事务上下文
|
||||
type TransactionContext struct {
|
||||
Tx *gorm.DB
|
||||
StartTime time.Time
|
||||
Config TransactionConfig
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// TransactionResult 事务结果
|
||||
type TransactionResult struct {
|
||||
Success bool `json:"success"`
|
||||
Duration time.Duration `json:"duration"`
|
||||
Error error `json:"error"`
|
||||
Retries int `json:"retries"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// NewTransactionManager 创建事务管理器
|
||||
func NewTransactionManager(db *gorm.DB, metrics *MetricsCollector, config *OptimizedConfig) *TransactionManager {
|
||||
return &TransactionManager{
|
||||
db: db,
|
||||
metrics: metrics,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// WithTransaction 在事务中执行操作
|
||||
func (tm *TransactionManager) WithTransaction(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) *TransactionResult {
|
||||
startTime := time.Now()
|
||||
result := &TransactionResult{
|
||||
Timestamp: startTime,
|
||||
}
|
||||
|
||||
// 设置默认配置
|
||||
if config.Timeout == 0 {
|
||||
config.Timeout = 30 * time.Second // 默认事务超时时间
|
||||
}
|
||||
if config.RetryAttempts == 0 {
|
||||
config.RetryAttempts = tm.config.RetryConfig.MaxRetries
|
||||
}
|
||||
if config.RetryDelay == 0 {
|
||||
config.RetryDelay = tm.config.RetryConfig.RetryDelay
|
||||
}
|
||||
|
||||
// 执行事务(带重试)
|
||||
for attempt := 1; attempt <= config.RetryAttempts; attempt++ {
|
||||
err := tm.executeTransaction(ctx, config, operation)
|
||||
if err == nil {
|
||||
result.Success = true
|
||||
result.Duration = time.Since(startTime)
|
||||
result.Retries = attempt - 1
|
||||
break
|
||||
}
|
||||
|
||||
result.Error = err
|
||||
result.Retries = attempt
|
||||
|
||||
// 检查是否可重试
|
||||
if !tm.isRetryableError(err) || attempt == config.RetryAttempts {
|
||||
break
|
||||
}
|
||||
|
||||
// 等待重试
|
||||
logger.Warnf("事务执行失败,第 %d 次重试: %v", attempt, err)
|
||||
time.Sleep(config.RetryDelay * time.Duration(attempt))
|
||||
}
|
||||
|
||||
result.Duration = time.Since(startTime)
|
||||
|
||||
// 记录指标
|
||||
if tm.metrics != nil {
|
||||
tm.metrics.RecordDbOperation(true, result.Duration, result.Error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// executeTransaction 执行单次事务
|
||||
func (tm *TransactionManager) executeTransaction(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) error {
|
||||
// 设置事务超时
|
||||
txCtx := ctx
|
||||
if config.Timeout > 0 {
|
||||
var cancel context.CancelFunc
|
||||
txCtx, cancel = context.WithTimeout(ctx, config.Timeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
// 开始事务
|
||||
tx := tm.db.WithContext(txCtx).Begin()
|
||||
if tx.Error != nil {
|
||||
return fmt.Errorf("开始事务失败: %w", tx.Error)
|
||||
}
|
||||
|
||||
// 设置事务隔离级别
|
||||
if config.IsolationLevel != "" {
|
||||
if err := tx.Exec(fmt.Sprintf("SET TRANSACTION ISOLATION LEVEL %s", config.IsolationLevel)).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("设置事务隔离级别失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置只读模式
|
||||
if config.ReadOnly {
|
||||
if err := tx.Exec("SET TRANSACTION READ ONLY").Error; err != nil {
|
||||
tx.Rollback()
|
||||
return fmt.Errorf("设置只读事务失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建事务上下文
|
||||
txContext := &TransactionContext{
|
||||
Tx: tx,
|
||||
StartTime: time.Now(),
|
||||
Config: config,
|
||||
Context: txCtx,
|
||||
}
|
||||
|
||||
// 执行操作
|
||||
err := operation(txContext)
|
||||
if err != nil {
|
||||
// 回滚事务
|
||||
if rollbackErr := tx.Rollback().Error; rollbackErr != nil {
|
||||
logger.Errorf("事务回滚失败: %v", rollbackErr)
|
||||
return fmt.Errorf("操作失败且回滚失败: %w, 回滚错误: %v", err, rollbackErr)
|
||||
}
|
||||
return fmt.Errorf("事务操作失败: %w", err)
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if commitErr := tx.Commit().Error; commitErr != nil {
|
||||
return fmt.Errorf("事务提交失败: %w", commitErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithOptimisticLock 使用乐观锁执行操作
|
||||
func (tm *TransactionManager) WithOptimisticLock(ctx context.Context, config TransactionConfig, operation func(*TransactionContext) error) *TransactionResult {
|
||||
return tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
// 在乐观锁模式下,我们需要在操作中检查版本号
|
||||
return operation(txCtx)
|
||||
})
|
||||
}
|
||||
|
||||
// SaveWithOptimisticLock 使用乐观锁保存数据
|
||||
func (tm *TransactionManager) SaveWithOptimisticLock(ctx context.Context, model interface{}, condition string, args ...interface{}) error {
|
||||
config := TransactionConfig{
|
||||
Timeout: 30 * time.Second,
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
result := tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
// 使用 WHERE 条件和版本号进行更新
|
||||
result := txCtx.Tx.Where(condition, args...).Save(model)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// 检查是否有行被更新
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("乐观锁冲突: 数据已被其他进程修改")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// UpdateWithOptimisticLock 使用乐观锁更新数据
|
||||
func (tm *TransactionManager) UpdateWithOptimisticLock(ctx context.Context, model interface{}, updates map[string]interface{}, condition string, args ...interface{}) error {
|
||||
config := TransactionConfig{
|
||||
Timeout: 30 * time.Second,
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
result := tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
// 增加版本号
|
||||
if _, hasVersion := updates["version"]; !hasVersion {
|
||||
updates["version"] = gorm.Expr("version + 1")
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
result := txCtx.Tx.Model(model).Where(condition, args...).Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// 检查是否有行被更新
|
||||
if result.RowsAffected == 0 {
|
||||
return fmt.Errorf("乐观锁冲突: 数据已被其他进程修改")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// BatchOperation 批量操作
|
||||
func (tm *TransactionManager) BatchOperation(ctx context.Context, config TransactionConfig, operations []func(*TransactionContext) error) *TransactionResult {
|
||||
return tm.WithTransaction(ctx, config, func(txCtx *TransactionContext) error {
|
||||
for i, operation := range operations {
|
||||
if err := operation(txCtx); err != nil {
|
||||
return fmt.Errorf("批量操作第 %d 步失败: %w", i+1, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// CreateSavepoint 创建保存点
|
||||
func (tm *TransactionManager) CreateSavepoint(txCtx *TransactionContext, name string) error {
|
||||
return txCtx.Tx.Exec(fmt.Sprintf("SAVEPOINT %s", name)).Error
|
||||
}
|
||||
|
||||
// RollbackToSavepoint 回滚到保存点
|
||||
func (tm *TransactionManager) RollbackToSavepoint(txCtx *TransactionContext, name string) error {
|
||||
return txCtx.Tx.Exec(fmt.Sprintf("ROLLBACK TO SAVEPOINT %s", name)).Error
|
||||
}
|
||||
|
||||
// ReleaseSavepoint 释放保存点
|
||||
func (tm *TransactionManager) ReleaseSavepoint(txCtx *TransactionContext, name string) error {
|
||||
return txCtx.Tx.Exec(fmt.Sprintf("RELEASE SAVEPOINT %s", name)).Error
|
||||
}
|
||||
|
||||
// isRetryableError 判断错误是否可重试
|
||||
func (tm *TransactionManager) isRetryableError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errorMsg := err.Error()
|
||||
|
||||
// 可重试的错误类型
|
||||
retryableErrors := []string{
|
||||
"deadlock",
|
||||
"lock wait timeout",
|
||||
"connection",
|
||||
"timeout",
|
||||
"temporary",
|
||||
"乐观锁冲突",
|
||||
}
|
||||
|
||||
for _, retryableError := range retryableErrors {
|
||||
if strings.Contains(errorMsg, retryableError) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetTransactionStats 获取事务统计信息
|
||||
func (tm *TransactionManager) GetTransactionStats() map[string]interface{} {
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
// 从指标收集器获取数据库相关统计
|
||||
if tm.metrics != nil {
|
||||
stats["total_transactions"] = tm.metrics.PerformanceMetrics.DbTransactions
|
||||
stats["total_queries"] = tm.metrics.PerformanceMetrics.DbQueries
|
||||
stats["total_errors"] = tm.metrics.PerformanceMetrics.DbErrors
|
||||
stats["avg_query_time"] = tm.metrics.PerformanceMetrics.DbQueryTime
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// 预定义的事务配置
|
||||
|
||||
// GetDefaultTransactionConfig 获取默认事务配置
|
||||
func GetDefaultTransactionConfig() TransactionConfig {
|
||||
return TransactionConfig{
|
||||
Timeout: 30 * time.Second,
|
||||
RetryAttempts: 3,
|
||||
RetryDelay: 100 * time.Millisecond,
|
||||
IsolationLevel: "READ COMMITTED",
|
||||
ReadOnly: false,
|
||||
}
|
||||
}
|
||||
|
||||
// GetReadOnlyTransactionConfig 获取只读事务配置
|
||||
func GetReadOnlyTransactionConfig() TransactionConfig {
|
||||
return TransactionConfig{
|
||||
Timeout: 10 * time.Second,
|
||||
RetryAttempts: 1,
|
||||
RetryDelay: 0,
|
||||
IsolationLevel: "READ COMMITTED",
|
||||
ReadOnly: true,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLongRunningTransactionConfig 获取长时间运行事务配置
|
||||
func GetLongRunningTransactionConfig() TransactionConfig {
|
||||
return TransactionConfig{
|
||||
Timeout: 300 * time.Second, // 5分钟
|
||||
RetryAttempts: 5,
|
||||
RetryDelay: 500 * time.Millisecond,
|
||||
IsolationLevel: "REPEATABLE READ",
|
||||
ReadOnly: false,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCriticalTransactionConfig 获取关键事务配置
|
||||
func GetCriticalTransactionConfig() TransactionConfig {
|
||||
return TransactionConfig{
|
||||
Timeout: 60 * time.Second,
|
||||
RetryAttempts: 5,
|
||||
RetryDelay: 200 * time.Millisecond,
|
||||
IsolationLevel: "SERIALIZABLE",
|
||||
ReadOnly: false,
|
||||
}
|
||||
}
|
||||
|
||||
// 全局事务管理器实例
|
||||
var GlobalTransactionManager *TransactionManager
|
||||
|
||||
// InitTransactionManager 初始化事务管理器
|
||||
func InitTransactionManager(db *gorm.DB, metrics *MetricsCollector, config *OptimizedConfig) {
|
||||
GlobalTransactionManager = NewTransactionManager(db, metrics, config)
|
||||
}
|
||||
|
||||
// GetTransactionManager 获取全局事务管理器
|
||||
func GetTransactionManager() *TransactionManager {
|
||||
return GlobalTransactionManager
|
||||
}
|
||||
|
||||
// WithDefaultTransaction 使用默认配置执行事务
|
||||
func WithDefaultTransaction(ctx context.Context, operation func(*TransactionContext) error) error {
|
||||
if GlobalTransactionManager == nil {
|
||||
return fmt.Errorf("事务管理器未初始化")
|
||||
}
|
||||
|
||||
config := GetDefaultTransactionConfig()
|
||||
result := GlobalTransactionManager.WithTransaction(ctx, config, operation)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// WithReadOnlyTransaction 使用只读配置执行事务
|
||||
func WithReadOnlyTransaction(ctx context.Context, operation func(*TransactionContext) error) error {
|
||||
if GlobalTransactionManager == nil {
|
||||
return fmt.Errorf("事务管理器未初始化")
|
||||
}
|
||||
|
||||
config := GetReadOnlyTransactionConfig()
|
||||
result := GlobalTransactionManager.WithTransaction(ctx, config, operation)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// WithCriticalTransaction 使用关键事务配置执行事务
|
||||
func WithCriticalTransaction(ctx context.Context, operation func(*TransactionContext) error) error {
|
||||
if GlobalTransactionManager == nil {
|
||||
return fmt.Errorf("事务管理器未初始化")
|
||||
}
|
||||
|
||||
config := GetCriticalTransactionConfig()
|
||||
result := GlobalTransactionManager.WithTransaction(ctx, config, operation)
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// SaveWithOptimisticLockGlobal 全局乐观锁保存
|
||||
func SaveWithOptimisticLockGlobal(ctx context.Context, model interface{}, condition string, args ...interface{}) error {
|
||||
if GlobalTransactionManager == nil {
|
||||
return fmt.Errorf("事务管理器未初始化")
|
||||
}
|
||||
|
||||
return GlobalTransactionManager.SaveWithOptimisticLock(ctx, model, condition, args...)
|
||||
}
|
||||
|
||||
// UpdateWithOptimisticLockGlobal 全局乐观锁更新
|
||||
func UpdateWithOptimisticLockGlobal(ctx context.Context, model interface{}, updates map[string]interface{}, condition string, args ...interface{}) error {
|
||||
if GlobalTransactionManager == nil {
|
||||
return fmt.Errorf("事务管理器未初始化")
|
||||
}
|
||||
|
||||
return GlobalTransactionManager.UpdateWithOptimisticLock(ctx, model, updates, condition, args...)
|
||||
}
|
||||
|
||||
// TransactionHelper 事务辅助函数集合
|
||||
type TransactionHelper struct {
|
||||
tm *TransactionManager
|
||||
}
|
||||
|
||||
// NewTransactionHelper 创建事务辅助器
|
||||
func NewTransactionHelper(tm *TransactionManager) *TransactionHelper {
|
||||
return &TransactionHelper{tm: tm}
|
||||
}
|
||||
|
||||
// SavePosition 保存持仓(带乐观锁)
|
||||
func (th *TransactionHelper) SavePosition(ctx context.Context, position interface{}, userID, symbol string, version int) error {
|
||||
condition := "user_id = ? AND symbol = ? AND version = ?"
|
||||
return th.tm.SaveWithOptimisticLock(ctx, position, condition, userID, symbol, version)
|
||||
}
|
||||
|
||||
// SaveOrder 保存订单(带乐观锁)
|
||||
func (th *TransactionHelper) SaveOrder(ctx context.Context, order interface{}, orderID string, version int) error {
|
||||
condition := "order_id = ? AND version = ?"
|
||||
return th.tm.SaveWithOptimisticLock(ctx, order, condition, orderID, version)
|
||||
}
|
||||
|
||||
// UpdateOrderStatus 更新订单状态(带乐观锁)
|
||||
func (th *TransactionHelper) UpdateOrderStatus(ctx context.Context, orderID, newStatus string, version int) error {
|
||||
updates := map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
condition := "order_id = ? AND version = ?"
|
||||
return th.tm.UpdateWithOptimisticLock(ctx, nil, updates, condition, orderID, version)
|
||||
}
|
||||
|
||||
// UpdatePositionQuantity 更新持仓数量(带乐观锁)
|
||||
func (th *TransactionHelper) UpdatePositionQuantity(ctx context.Context, userID, symbol string, quantity float64, version int) error {
|
||||
updates := map[string]interface{}{
|
||||
"quantity": quantity,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
condition := "user_id = ? AND symbol = ? AND version = ?"
|
||||
return th.tm.UpdateWithOptimisticLock(ctx, nil, updates, condition, userID, symbol, version)
|
||||
}
|
||||
|
||||
// GetGlobalTransactionHelper 获取全局事务辅助器
|
||||
func GetGlobalTransactionHelper() *TransactionHelper {
|
||||
if GlobalTransactionManager == nil {
|
||||
return nil
|
||||
}
|
||||
return NewTransactionHelper(GlobalTransactionManager)
|
||||
}
|
||||
Reference in New Issue
Block a user