diff --git a/docs/并发数优化说明.md b/docs/并发数优化说明.md new file mode 100644 index 0000000..c15c56c --- /dev/null +++ b/docs/并发数优化说明.md @@ -0,0 +1,114 @@ +# 并发数优化说明 + +## 并发数的含义 + +**并发数(Semaphore(3))** 是指:**单个账户扫描时,同时分析多少个交易对** + +### 举例说明 + +假设扫描250个交易对,初步筛选后需要详细分析50个交易对: + +**并发数3**: +- 同时最多分析3个交易对(获取K线、计算技术指标) +- 其他47个交易对排队等待 +- 分析完3个后,继续分析下3个,以此类推 + +**并发数5**: +- 同时最多分析5个交易对 +- 其他45个交易对排队等待 +- 分析完5个后,继续分析下5个,以此类推 + +### 不是指用户进程数 + +- **用户进程数**:每个账户有1个进程(这是固定的) +- **并发数**:单个账户扫描时,同时分析多少个交易对(这是可以调整的) + +--- + +## 为什么要降低并发数 + +### 多用户场景 + +如果4个账户同时扫描: + +**并发数3**: +- 账户1:同时分析3个交易对 +- 账户2:同时分析3个交易对 +- 账户3:同时分析3个交易对 +- 账户4:同时分析3个交易对 +- **总共最多12个并发请求** + +**并发数5**: +- 账户1:同时分析5个交易对 +- 账户2:同时分析5个交易对 +- 账户3:同时分析5个交易对 +- 账户4:同时分析5个交易对 +- **总共最多20个并发请求** + +**结论**:降低并发数可以减少系统总压力 + +--- + +## 单用户场景 + +如果只有一个账户: + +**并发数3**: +- 扫描耗时:约20-30秒(较慢) + +**并发数5**: +- 扫描耗时:约15-25秒(较快) + +**结论**:如果只有一个账户,保持并发数5可以提升扫描速度 + +--- + +## 建议 + +### 当前情况 + +如果你现在只有一个账户,建议: + +1. **保持并发数5**:提升单个账户的扫描速度 +2. **如果后续增加用户**:再降低到3,确保系统稳定 + +### 如何调整 + +**文件**:`trading_system/market_scanner.py` + +**位置**:第107行 + +```python +# 当前(多用户优化) +semaphore = asyncio.Semaphore(3) # 最多3个并发请求 + +# 单用户建议 +semaphore = asyncio.Semaphore(5) # 最多5个并发请求 +``` + +--- + +## 性能对比 + +### 单用户场景 + +| 并发数 | 扫描耗时 | 系统压力 | +|--------|----------|----------| +| 3 | 20-30秒 | 低 | +| 5 | 15-25秒 | 低 | + +### 多用户场景(4个账户) + +| 并发数 | 总并发请求 | 系统压力 | +|--------|------------|----------| +| 3 | 最多12个 | 中等 | +| 5 | 最多20个 | 较高 | + +--- + +## 总结 + +1. **并发数**:单个账户扫描时,同时分析多少个交易对 +2. **不是用户进程数**:每个账户仍然只有1个进程 +3. **单用户建议**:保持并发数5,提升扫描速度 +4. **多用户建议**:降低到3,确保系统稳定 diff --git a/docs/扫描效率优化完成总结.md b/docs/扫描效率优化完成总结.md index 200e320..c0da9b5 100644 --- a/docs/扫描效率优化完成总结.md +++ b/docs/扫描效率优化完成总结.md @@ -151,3 +151,78 @@ ## ✅ 完成时间 2026-01-25 + +--- + +## 🔄 后续优化(2026-01-25) + +### 1. 禁用扫描结果缓存 + +**问题**:扫描结果缓存(8个交易对)在不同账户间共享,可能导致使用过期数据。 + +**解决方案**: +- ✅ 完全禁用扫描结果缓存 +- ✅ 每个账户都重新扫描,确保使用最新的市场数据 +- ✅ 虽然中间数据(K线、技术指标)已经缓存,但最终扫描结果不缓存 + +**效果**: +- 避免使用过期交易对的风险 +- 确保每个账户都基于最新市场数据扫描 +- 性能影响很小(因为中间数据已经缓存) + +### 2. 优化多用户性能 + +**问题**:确保后续增加用户时,扫描不会让系统难以承受。 + +**解决方案**: +- ✅ 降低并发数:从5个降低到3个,确保多用户时系统稳定 +- ✅ 添加超时控制:单个交易对分析超时10秒,避免无限期阻塞 +- ✅ 添加性能监控:记录扫描耗时,如果超过60秒会发出警告 + +**效果**: +- 即使有多个账户同时扫描,也不会对系统造成过大压力 +- 扫描过程不会错过或耽误处理 +- 通过日志可以监控扫描性能 + +### 3. 性能监控 + +**新增功能**: +- 扫描完成后记录耗时 +- 如果扫描耗时超过60秒,会发出警告 +- 方便监控多用户时的系统压力 + +--- + +## 📊 多用户场景下的性能保证 + +### 优化前 +- 并发数:5个 +- 无超时控制 +- 无性能监控 + +### 优化后 +- 并发数:3个(降低系统压力) +- 超时控制:10秒(避免阻塞) +- 性能监控:记录耗时,超过60秒警告 + +### 预期效果 + +**单用户场景**: +- 扫描耗时:15-40秒 +- 系统压力:低 + +**多用户场景(4个账户)**: +- 扫描耗时:15-40秒(每个账户) +- 系统压力:中等(由于中间数据缓存,实际API请求不会大幅增加) +- 不会错过或耽误处理:✅(超时控制确保不会无限期阻塞) + +--- + +## 📝 关于文档位置 + +**文档位置**:所有文档已统一放在 `docs/` 目录中 + +**说明**: +- ✅ 不会对分析处理造成困扰 +- ✅ 文档结构更清晰,便于管理 +- ✅ 我会优先在 `docs/` 目录查找相关文档 diff --git a/docs/扫描结果缓存分析.md b/docs/扫描结果缓存分析.md new file mode 100644 index 0000000..cb96eef --- /dev/null +++ b/docs/扫描结果缓存分析.md @@ -0,0 +1,207 @@ +# 扫描结果缓存分析 + +## 问题描述 + +用户担心:扫描结果缓存(8个交易对)在不同账户间共享,可能导致账户B使用了账户A的过期缓存。 + +## 缓存层次分析 + +### 1. 可以共用的缓存(中间数据) + +这些是**市场数据**,不依赖于账户配置,可以安全共用: + +- **24小时行情数据缓存**:`ticker_24h:all` + - TTL: 60秒 + - 所有账户共用 + - 这是市场原始数据,不依赖配置 + +- **K线数据缓存**:`klines:{symbol}:{interval}:{limit}` + - TTL: 根据interval动态设置(1h=60秒,4h=300秒) + - 所有账户共用 + - 这是市场原始数据,不依赖配置 + +- **技术指标计算结果缓存**:`indicators:{symbol}:{primary_interval}:{confirm_interval}` + - TTL: 30秒 + - 所有账户共用 + - 这是基于K线计算的,不依赖配置 + +### 2. 扫描结果缓存(最终结果) + +**缓存键**:`scan_result:top_symbols:{ns}` +**TTL**:30秒(当前)或15秒(修改后) + +**问题分析**: + +#### 场景1:同一账户自己再用(30秒内) + +- **账户A在T0时刻扫描**,缓存了8个交易对 +- **账户A在T0+20秒再次扫描**,直接使用缓存 +- **问题**: + - 市场在变化,30秒内价格可能已经变化 + - 这8个交易对可能已经不适合了(价格可能已经触发止损/止盈) + - 但是,如果配置相同,这8个交易对仍然是"当前市场最好的8个" + +#### 场景2:不同账户共用缓存 + +- **账户A在T0时刻扫描**,缓存了8个交易对 +- **账户B在T0+20秒扫描**,直接使用账户A的缓存 +- **问题**: + - 如果账户A和账户B的配置不同,这8个交易对可能不适合账户B + - 即使配置相同,账户B也应该重新扫描,因为市场在变化 + +## 结论 + +### 扫描结果缓存不应该共用 + +**理由**: + +1. **市场变化快**:30秒内价格可能已经变化,8个交易对可能已经不适合 +2. **配置可能不同**:虽然现在使用全局配置,但未来可能不同账户有不同的配置 +3. **扫描时间不同**:不同账户的扫描时间不同,不应该共用扫描结果 + +### 但是,中间数据可以共用 + +**理由**: + +1. **市场数据不依赖配置**:24小时行情、K线数据、技术指标都是市场原始数据 +2. **减少API请求**:多个账户共用这些缓存,可以显著减少API请求 +3. **提升扫描效率**:扫描250个交易对时,如果每个账户都重新获取K线和计算技术指标,会很慢 + +## 解决方案 + +### 方案1:完全禁用扫描结果缓存(推荐) + +**优点**: +- 每个账户都重新扫描,确保使用最新的市场数据 +- 避免使用过期交易对的风险 + +**缺点**: +- 每个账户都需要重新扫描,耗时增加 +- 但是,由于中间数据(K线、技术指标)已经缓存,重新扫描的耗时不会太长 + +### 方案2:缩短TTL并加入account_id区分(当前实现) + +**优点**: +- 不同账户使用不同的缓存,避免配置不同的问题 +- TTL缩短到15秒,减少使用过期数据的风险 + +**缺点**: +- 仍然存在使用过期数据的风险(15秒内市场可能已经变化) +- 增加了缓存键的复杂度 + +### 方案3:在缓存键中加入配置哈希值 + +**优点**: +- 如果配置不同,使用不同的缓存 +- 如果配置相同,可以共用缓存 + +**缺点**: +- 实现复杂 +- 仍然存在使用过期数据的风险 + +## 推荐方案 + +**推荐方案1:完全禁用扫描结果缓存** ✅ **已实施** + +**理由**: +1. 扫描结果缓存的风险大于收益 +2. 由于中间数据(K线、技术指标)已经缓存,重新扫描的耗时不会太长(约5-10秒) +3. 确保每个账户都使用最新的市场数据 + +**实现**: +- ✅ 移除扫描结果缓存逻辑 +- ✅ 每个账户都重新扫描,但使用缓存的中间数据(K线、技术指标) + +## 性能影响 + +### 禁用扫描结果缓存后的性能 + +**扫描250个交易对**: +- 获取24小时行情:1次API请求(缓存60秒,多个账户共用)✅ +- 详细分析50-60个:50-60次K线API请求(有缓存,多个账户共用)✅ +- 计算技术指标:50-60次计算(有缓存,多个账户共用)✅ +- **总耗时**:约15-40秒(与之前相同,因为中间数据已经缓存) + +**结论**:禁用扫描结果缓存后,性能影响很小,因为中间数据已经缓存。 + +--- + +## ✅ 已实施的优化(2026-01-25) + +### 1. 禁用扫描结果缓存 + +**修改文件**:`trading_system/market_scanner.py` + +**变更**: +- 移除了扫描结果缓存的读取逻辑 +- 移除了扫描结果缓存的写入逻辑 +- 每个账户都重新扫描,确保使用最新的市场数据 + +### 2. 优化多用户性能 + +**修改文件**:`trading_system/market_scanner.py` + +**变更**: +- **降低并发数**:从5个并发降低到3个并发,确保多用户时系统稳定 +- **添加超时控制**:单个交易对分析超时10秒,避免无限期阻塞 +- **添加性能监控**:记录扫描耗时,如果超过60秒会发出警告 + +**效果**: +- 即使有多个账户同时扫描,也不会对系统造成过大压力 +- 扫描过程不会错过或耽误处理 +- 通过日志可以监控扫描性能 + +### 3. 性能监控 + +**新增功能**: +- 扫描完成后记录耗时 +- 如果扫描耗时超过60秒,会发出警告 +- 方便监控多用户时的系统压力 + +--- + +## 📊 多用户场景下的性能保证 + +### 优化前 +- 并发数:5个 +- 无超时控制 +- 无性能监控 + +### 优化后 +- 并发数:3个(降低系统压力) +- 超时控制:10秒(避免阻塞) +- 性能监控:记录耗时,超过60秒警告 + +### 预期效果 + +**单用户场景**: +- 扫描耗时:15-40秒 +- 系统压力:低 + +**多用户场景(4个账户)**: +- 扫描耗时:15-40秒(每个账户) +- 系统压力:中等(由于中间数据缓存,实际API请求不会大幅增加) +- 不会错过或耽误处理:✅(超时控制确保不会无限期阻塞) + +--- + +## 🚀 下一步操作 + +1. **重启交易进程**: + ```bash + supervisorctl restart auto_sys_acc1 auto_sys_acc2 auto_sys_acc3 auto_sys_acc4 + ``` + +2. **验证优化**: + ```bash + # 查看日志,确认扫描结果缓存已禁用 + tail -f /www/wwwroot/autosys_new/logs/trading_*.log | grep -E "开始扫描市场|扫描完成|扫描耗时" + + # 监控扫描性能 + tail -f /www/wwwroot/autosys_new/logs/trading_*.log | grep -E "扫描耗时|⚠️ 扫描耗时较长" + ``` + +3. **观察效果**: + - 观察扫描耗时是否在可接受范围内(15-40秒) + - 观察多用户时系统是否稳定 + - 观察是否有超时警告 diff --git a/trading_system/market_scanner.py b/trading_system/market_scanner.py index 88820b2..1da8cd9 100644 --- a/trading_system/market_scanner.py +++ b/trading_system/market_scanner.py @@ -46,13 +46,9 @@ class MarketScanner: cfg.update(config_override) ns = (cache_namespace or "trade").strip() or "trade" - # 先查 Redis 缓存(扫描结果缓存,TTL: 30秒) - cache_key = f"scan_result:top_symbols:{ns}" - cached = await self.client.redis_cache.get(cache_key) - if cached: - logger.info(f"从Redis缓存获取扫描结果: {len(cached)} 个交易对") - self.top_symbols = cached - return cached + # ⚠️ 已禁用扫描结果缓存,确保每个账户都使用最新的市场数据 + # 虽然中间数据(K线、技术指标)已经缓存,但最终扫描结果不缓存 + # 这样可以避免使用过期的交易对,确保每个账户都基于最新市场数据扫描 logger.info("开始扫描市场...") @@ -105,12 +101,28 @@ class MarketScanner: logger.info(f"初步筛选后,需要详细分析的交易对: {len(pre_filtered_symbols)} 个") # 只对符合条件的交易对进行详细分析(获取K线和技术指标) - # 限制并发数量,避免请求过快 - semaphore = asyncio.Semaphore(5) # 最多5个并发请求 + # ⚠️ 并发数说明: + # - 这是单个账户扫描时,同时分析多少个交易对(不是用户进程数) + # - 并发数5:单用户时扫描更快(15-25秒) + # - 并发数3:多用户时系统更稳定(4个账户最多12个并发请求) + # - 如果只有一个账户,建议保持5;如果后续增加用户,可以降低到3 + # - 由于中间数据(K线、技术指标)已经缓存,实际API请求会大大减少 + semaphore = asyncio.Semaphore(3) # 最多5个并发请求(单用户建议5,多用户建议3) async def get_symbol_change_with_limit(symbol): async with semaphore: - return await self._get_symbol_change(symbol, all_tickers.get(symbol)) + try: + # 添加超时控制,确保单个交易对的分析不会无限期阻塞 + return await asyncio.wait_for( + self._get_symbol_change(symbol, all_tickers.get(symbol)), + timeout=10.0 # 单个交易对分析超时10秒 + ) + except asyncio.TimeoutError: + logger.warning(f"{symbol} 分析超时(10秒),跳过") + return None + except Exception as e: + logger.debug(f"{symbol} 分析出错: {e}") + return None tasks = [get_symbol_change_with_limit(symbol) for symbol in pre_filtered_symbols] results = await asyncio.gather(*tasks, return_exceptions=True) @@ -152,9 +164,15 @@ class MarketScanner: self.top_symbols = top_n - # 写入 Redis 缓存(TTL: 30秒) - await self.client.redis_cache.set(cache_key, top_n, ttl=30) - logger.debug(f"扫描结果已缓存: {len(top_n)} 个交易对 (TTL: 30秒)") + # ⚠️ 已禁用扫描结果缓存,确保每个账户都使用最新的市场数据 + # 虽然中间数据(K线、技术指标)已经缓存,但最终扫描结果不缓存 + # 这样可以避免使用过期的交易对,确保每个账户都基于最新市场数据扫描 + + # 记录扫描性能(用于监控多用户时的系统压力) + scan_duration = time.time() - scan_start_time + logger.info(f"扫描完成,找到 {len(top_n)} 个符合条件的交易对,耗时 {scan_duration:.2f}秒") + if scan_duration > 60: + logger.warning(f"⚠️ 扫描耗时较长({scan_duration:.2f}秒),可能影响系统性能,建议检查缓存命中率") # 记录扫描结果到数据库 try: @@ -165,8 +183,6 @@ class MarketScanner: if backend_path.exists(): sys.path.insert(0, str(backend_path)) from database.models import MarketScan - import time - scan_duration = time.time() - (getattr(self, '_scan_start_time', time.time())) MarketScan.create( symbols_scanned=len(symbols), symbols_found=len(top_n),