This commit is contained in:
薇薇安 2026-01-25 16:53:40 +08:00
parent 83e628b611
commit c3a14f0f1a
4 changed files with 427 additions and 15 deletions

View File

@ -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确保系统稳定

View File

@ -151,3 +151,78 @@
## ✅ 完成时间 ## ✅ 完成时间
2026-01-25 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/` 目录查找相关文档

View File

@ -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秒
- 观察多用户时系统是否稳定
- 观察是否有超时警告

View File

@ -46,13 +46,9 @@ class MarketScanner:
cfg.update(config_override) cfg.update(config_override)
ns = (cache_namespace or "trade").strip() or "trade" ns = (cache_namespace or "trade").strip() or "trade"
# 先查 Redis 缓存扫描结果缓存TTL: 30秒 # ⚠️ 已禁用扫描结果缓存,确保每个账户都使用最新的市场数据
cache_key = f"scan_result:top_symbols:{ns}" # 虽然中间数据K线、技术指标已经缓存但最终扫描结果不缓存
cached = await self.client.redis_cache.get(cache_key) # 这样可以避免使用过期的交易对,确保每个账户都基于最新市场数据扫描
if cached:
logger.info(f"从Redis缓存获取扫描结果: {len(cached)} 个交易对")
self.top_symbols = cached
return cached
logger.info("开始扫描市场...") logger.info("开始扫描市场...")
@ -105,12 +101,28 @@ class MarketScanner:
logger.info(f"初步筛选后,需要详细分析的交易对: {len(pre_filtered_symbols)}") logger.info(f"初步筛选后,需要详细分析的交易对: {len(pre_filtered_symbols)}")
# 只对符合条件的交易对进行详细分析获取K线和技术指标 # 只对符合条件的交易对进行详细分析获取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 def get_symbol_change_with_limit(symbol):
async with semaphore: 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] tasks = [get_symbol_change_with_limit(symbol) for symbol in pre_filtered_symbols]
results = await asyncio.gather(*tasks, return_exceptions=True) results = await asyncio.gather(*tasks, return_exceptions=True)
@ -152,9 +164,15 @@ class MarketScanner:
self.top_symbols = top_n self.top_symbols = top_n
# 写入 Redis 缓存TTL: 30秒 # ⚠️ 已禁用扫描结果缓存,确保每个账户都使用最新的市场数据
await self.client.redis_cache.set(cache_key, top_n, ttl=30) # 虽然中间数据K线、技术指标已经缓存但最终扫描结果不缓存
logger.debug(f"扫描结果已缓存: {len(top_n)} 个交易对 (TTL: 30秒)") # 这样可以避免使用过期的交易对,确保每个账户都基于最新市场数据扫描
# 记录扫描性能(用于监控多用户时的系统压力)
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: try:
@ -165,8 +183,6 @@ class MarketScanner:
if backend_path.exists(): if backend_path.exists():
sys.path.insert(0, str(backend_path)) sys.path.insert(0, str(backend_path))
from database.models import MarketScan from database.models import MarketScan
import time
scan_duration = time.time() - (getattr(self, '_scan_start_time', time.time()))
MarketScan.create( MarketScan.create(
symbols_scanned=len(symbols), symbols_scanned=len(symbols),
symbols_found=len(top_n), symbols_found=len(top_n),