-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Closed
Description
背景 / Background
PR #176 已经成功合并,实现了 WebSocket K线数据缓存,性能提升显著:
- ⚡ 延迟从 2-5分钟降至 <100ms
- 💰 API 消耗接近 0%
- 📊 实时数据推送
但在 code review 中发现了一些需要后续优化的问题。
需要修复的问题 / Issues to Fix
P0 - Critical (必须修复)
1. ❌ market.Get() 缺少 nil 检查
位置: market/data.go:21
问题:
func Get(symbol string) (*Data, error) {
klines3m, err = WSMonitorCli.GetCurrentKlines(symbol, "3m") // ← 如果 WSMonitorCli 为 nil 会 panic影响: 如果 WebSocket 监控器未初始化就调用 market.Get(),程序会 panic
修复:
func Get(symbol string) (*Data, error) {
if WSMonitorCli == nil {
return nil, fmt.Errorf("WebSocket监控器未初始化")
}
klines3m, err = WSMonitorCli.GetCurrentKlines(symbol, "3m")
// ...
}P1 - High Priority (强烈建议)
2. 🔄 缺少动态币种订阅机制
问题:
- 当前只在启动时订阅
database.GetCustomCoins() - 如果运行时通过 Web UI 添加新交易员或修改币种配置
- 新币种不会被自动订阅
影响:
- 新添加的交易员无法获取 K线数据
- 用户体验差
建议实现:
// market/monitor.go
func (m *WSMonitor) AddSymbol(symbol string) error {
// 检查是否已订阅
if _, exists := m.klineDataMap3m.Load(symbol); exists {
return nil // 已存在
}
// 获取历史数据
apiClient := NewAPIClient()
klines3m, err := apiClient.GetKlines(symbol, "3m", 100)
if err != nil {
return fmt.Errorf("获取历史数据失败: %v", err)
}
klines4h, err := apiClient.GetKlines(symbol, "4h", 100)
if err != nil {
return fmt.Errorf("获取历史数据失败: %v", err)
}
// 存储历史数据
m.klineDataMap3m.Store(symbol, klines3m)
m.klineDataMap4h.Store(symbol, klines4h)
// 订阅新币种
m.subscribeSymbol(symbol, "3m")
m.subscribeSymbol(symbol, "4h")
return nil
}
// manager/trader_manager.go
func (tm *TraderManager) StartTrader(traderID string) error {
// ... 启动交易员逻辑
// 动态添加币种订阅
for _, symbol := range trader.GetTradingSymbols() {
if err := market.WSMonitorCli.AddSymbol(symbol); err != nil {
log.Printf("⚠️ 添加币种订阅失败 %s: %v", symbol, err)
}
}
return nil
}3. 🧹 缺少优雅关闭机制
位置: market/monitor.go
问题: 没有 Stop() 方法来清理 WebSocket 连接和 goroutines
影响:
- 程序退出时 WebSocket 连接未关闭
- goroutines 泄漏
建议实现:
// market/monitor.go
func (m *WSMonitor) Stop() error {
log.Println("📴 正在关闭 WebSocket 监控器...")
// 取消所有订阅
if m.combinedClient != nil {
m.combinedClient.Close()
}
// 关闭 WebSocket 连接
if m.wsClient != nil && m.wsClient.conn != nil {
m.wsClient.conn.Close()
}
// 关闭 channels
close(m.alertsChan)
log.Println("✅ WebSocket 监控器已关闭")
return nil
}// main.go
func main() {
// ... 启动逻辑
// 启动 WebSocket 监控器
go market.NewWSMonitor(150).Start(database.GetCustomCoins())
// 设置优雅退出
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
log.Println("🛑 收到退出信号...")
// 优雅关闭
if market.WSMonitorCli != nil {
market.WSMonitorCli.Stop()
}
os.Exit(0)
}4. 🐌 币种去重算法低效
位置: config/database.go:950
问题:
for _, s := range strings.Split(symbol, ",") {
if s == "" {
continue
}
coin := market.Normalize(s)
if !slices.Contains(symbols, coin) { // ← O(n²) 复杂度
symbols = append(symbols, coin)
}
}影响: 当币种数量较多时性能差
建议优化:
func (d *Database) GetCustomCoins() []string {
var symbol string
var symbols []string
_ = d.db.QueryRow(`
SELECT GROUP_CONCAT(custom_coins , ',') as symbol
FROM main.traders where custom_coins != ''
`).Scan(&symbol)
// 检测用户是否未配置币种
if symbol == "" {
symbolJSON, _ := d.GetSystemConfig("default_coins")
if err := json.Unmarshal([]byte(symbolJSON), &symbols); err != nil {
log.Printf("⚠️ 解析default_coins配置失败: %v,使用硬编码默认值", err)
symbols = []string{"BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT"}
}
return symbols
}
// 使用 map 去重 - O(n) 复杂度
symbolMap := make(map[string]bool)
// 添加已有的默认币种
for _, s := range symbols {
symbolMap[s] = true
}
// 添加用户自定义币种
for _, s := range strings.Split(symbol, ",") {
if s == "" {
continue
}
coin := market.Normalize(s)
symbolMap[coin] = true
}
// 转换为切片
result := make([]string, 0, len(symbolMap))
for coin := range symbolMap {
result = append(result, coin)
}
return result
}P2 - Medium Priority (优化建议)
5. ⏱️ 缺少数据新鲜度检查
问题: 缓存的 K线数据没有时间戳验证
影响: 如果 WebSocket 断开较长时间,缓存数据会过时但仍被使用
建议: 添加数据新鲜度检查
func (m *WSMonitor) GetCurrentKlines(symbol, interval string) ([]Kline, error) {
// 获取缓存
var klines []Kline
if interval == "3m" {
data, ok := m.klineDataMap3m.Load(symbol)
if !ok {
return nil, fmt.Errorf("未找到币种: %s", symbol)
}
klines = data.([]Kline)
}
// 检查数据新鲜度
if len(klines) > 0 {
lastKline := klines[len(klines)-1]
age := time.Since(time.Unix(lastKline.CloseTime/1000, 0))
maxAge := 5 * time.Minute // 3m K线最多5分钟过期
if interval == "4h" {
maxAge = 10 * time.Minute
}
if age > maxAge {
log.Printf("⚠️ %s K线数据过期 (%.1f分钟前), 需要刷新", symbol, age.Minutes())
// 可以选择回退到 REST API 或返回错误
}
}
return klines, nil
}6. 🚀 初始化并发数可优化
位置: market/monitor.go:86
当前:
semaphore := make(chan struct{}, 5) // 并发数仅5建议: 提高到 10-20,加快初始化速度
实施建议 / Implementation Plan
- 阶段1: 修复 P0 问题(nil 检查)- 立即
- 阶段2: 实现 P1 功能(动态订阅、优雅关闭)- 本周
- 阶段3: 优化 P2 项目(数据新鲜度、性能)- 下周
参考 / References
- PR Kline获取方式为Websocket缓存 #176: Kline获取方式为Websocket缓存 #176
- Issue Add K-line data caching to reduce Binance API calls #254: K线缓存优化讨论
- Code Review: Kline获取方式为Websocket缓存 #176 (review)
优先级: P0 + P1 建议在下一个版本修复
Metadata
Metadata
Assignees
Labels
No labels