Spaces:
Running
Running
File size: 8,282 Bytes
3a08f21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | # Worker Pool 饱和问题修复方案
## 问题背景
在 HuggingFace Space 部署的 stock_data_api_service 中,以下端点频繁出现 timeout / cooldown / worker_pool_saturated 错误:
- `stock_search`(`/api/v1/search`)
- `boards_flow`(`/api/v1/boards/flow`、`/boards/concepts/flow`、`/boards/industries/flow`)
- `sector_concept_hot`(`/api/v1/boards/concepts/flow`)
### 根本原因
1. **HuggingFace Space 的 Worker 限制**
- Free tier:1-2 个并发 worker
- Pro tier:10+ 个并发 worker
- 每个慢请求(15-60s)会占用一个 worker,导致 worker pool 饱和
2. **无全局并发控制**
- `source_runner.py` 中的 `call_with_timeout` 每次都创建新的 `ThreadPoolExecutor`
- 没有全局的并发限制和请求排队机制
- 多个慢请求同时到达,立即超出 HF 限制
3. **网络请求慢**
- eastmoney/AKShare 接口响应时间不稳定(15-60s+)
- 当前 `SOURCE_TIMEOUT_SECONDS = 15` 太短,但增加超时会占用更多 worker
## 修复方案
### 1. 引入全局源调用池(GlobalSourcePool)
**修改文件**: `app/services/source_runner.py`
```python
class GlobalSourcePool:
"""全局源调用池,控制并发数,防止 worker pool 饱和"""
def __init__(self, max_concurrent: int = 2, max_workers: int = 4):
self._max_concurrent = max_concurrent
self._semaphore = threading.Semaphore(max_concurrent)
self._executor = concurrent.futures.ThreadPoolExecutor(
max_workers=max_workers,
thread_name_prefix="source_pool"
)
self._stats = {
"active": 0,
"queued": 0,
"completed": 0,
"failed": 0,
"timeout": 0,
}
```
**关键特性**:
- ✅ 使用 `Semaphore` 控制最大并发数
- ✅ 线程池复用,不再每次创建新的 ThreadPoolExecutor
- ✅ 统计指标(active, queued, completed, failed, timeout)
- ✅ 懒初始化,全局单例
### 2. 添加环境变量配置
**修改文件**: `app/core/config.py`
```python
# 源调用池配置
max_concurrent_sources: int = _int_env("MAX_CONCURRENT_SOURCES", 2) # HF Free: 2, Pro: 10
source_pool_workers: int = _int_env("SOURCE_POOL_WORKERS", 4) # 线程池大小
```
**配置项说明**:
| 配置项 | 默认值 | HF Free | HF Pro | 说明 |
|---|---|---|---|---|
| `MAX_CONCURRENT_SOURCES` | 2 | 2 | 10 | 最大并发源调用数 |
| `SOURCE_POOL_WORKERS` | 4 | 4 | 10 | 线程池大小 |
| `SOURCE_TIMEOUT_SECONDS` | 15 | 15-30 | 15 | 单个请求超时 |
### 3. 添加监控端点
**修改文件**: `app/api/routes.py`
```python
@router.get("/admin/source-pool-stats")
def source_pool_stats():
"""获取源调用池的统计信息"""
stats = get_source_pool_stats()
return {
"pool_stats": stats,
"recommendations": {
"worker_pool_saturated": stats.get("active", 0) >= stats.get("max_concurrent", 2),
"suggestion": "如果 active >= max_concurrent,说明 worker pool 已饱和",
},
}
@router.get("/admin/health-detailed")
def health_detailed():
"""详细的健康检查"""
pool_stats = get_source_pool_stats()
return {
"ok": True,
"version": "v24-concurrent-control",
"source_pool": pool_stats,
"config": {...},
}
```
## 部署指南
### 步骤 1:更新环境变量
在 HuggingFace Space 的 Settings > Repository secrets 中添加:
```
MAX_CONCURRENT_SOURCES=2 # HF Free tier 保持 2
SOURCE_POOL_WORKERS=4 # 线程池大小
SOURCE_TIMEOUT_SECONDS=15 # 可以适当增加到 30(如果响应慢)
```
### 步骤 2:提交代码
```bash
cd stock_data_api_service
git add -A
git commit -m "fix: 引入全局源调用池,防止 HF worker pool 饱和
- 添加 GlobalSourcePool 类,使用 Semaphore 控制并发数
- 配置 MAX_CONCURRENT_SOURCES 和 SOURCE_POOL_WORKERS 环境变量
- 添加 /admin/source-pool-stats 和 /admin/health-detailed 监控端点
- 复用线程池,避免每次请求创建新的 ThreadPoolExecutor
- 问题:board_flow/sector_concept_hot 端点因无并发控制导致 worker_pool_saturated"
```
### 步骤 3:验证修复
1. **监控端点检查**:
```bash
curl -H "X-API-Key: YOUR_KEY" https://your-space.hf.space/api/v1/admin/health-detailed
```
2. **并发测试**:
```bash
# 同时发起 5 个并发请求(HF Free tier 只有 2 个 worker)
for i in {1..5}; do
curl -H "X-API-Key: YOUR_KEY" \
"https://your-space.hf.space/api/v1/boards/flow?category=industry&limit=10" &
done
wait
```
3. **查看统计**:
```bash
curl -H "X-API-Key: YOUR_KEY" https://your-space.hf.space/api/v1/admin/source-pool-stats
```
## 调优指南
### 场景 1:Free tier(1-2 workers)
**配置**:
```env
MAX_CONCURRENT_SOURCES=2
SOURCE_POOL_WORKERS=2
SOURCE_TIMEOUT_SECONDS=20
```
**特点**:
- 最大 2 个并发请求,其他排队等待
- 线程池大小 2,避免线程切换开销
- 超时 20s,给上游足够时间响应
### 场景 2:Pro tier(10+ workers)
**配置**:
```env
MAX_CONCURRENT_SOURCES=10
SOURCE_POOL_WORKERS=10
SOURCE_TIMEOUT_SECONDS=15
```
**特点**:
- 最大 10 个并发请求
- 线程池大小 10
- 超时 15s
### 场景 3:上游响应慢(60s+)
**配置**:
```env
MAX_CONCURRENT_SOURCES=2
SOURCE_POOL_WORKERS=2
SOURCE_TIMEOUT_SECONDS=30
```
**特点**:
- 减少并发数,避免 worker 被长时间占用
- 增加超时时间,给上游更多时间
## 性能指标
### 修复前
- ❌ `worker_pool_saturated` 错误:多个并发请求时立即饱和
- ❌ 请求排队无限制,所有请求竞争
- ❌ 每次请求创建新的线程池,资源浪费
### 修复后
- ✅ 最多 N 个并发请求(N = MAX_CONCURRENT_SOURCES)
- ✅ 超出的请求排队等待,而不是立即饱和
- ✅ 线程池复用,资源效率提升
- ✅ 统计指标可用于监控和调优
## 常见问题
### Q1:为什么 `boards_flow` 和 `sector_concept_hot` 容易饱和?
**A1**:这些端点的特征:
- 调用 eastmoney/AKShare 的网络接口
- 响应时间不稳定(15-60s)
- 没有本地缓存(不像 `/stocks/{code}/daily` 有长期缓存)
- 并发调用时立即占用所有 worker
### Q2:如何判断 worker pool 是否饱和?
**A2**:调用 `/admin/source-pool-stats`,查看:
```json
{
"pool_stats": {
"active": 2, // 当前活跃请求数
"queued": 5, // 排队等待的请求数
"max_concurrent": 2, // 最大并发数
"completed": 100, // 已完成请求数
"timeout": 10, // 超时请求数
}
}
```
如果 `active >= max_concurrent`,说明 worker pool 已饱和。
### Q3:应该增加 MAX_CONCURRENT_SOURCES 还是增加 SOURCE_TIMEOUT_SECONDS?
**A3**:取决于你的 HF tier 和上游响应时间:
| 情况 | 建议 |
|---|---|
| HF Free tier + 上游响应快(<15s) | 保持 MAX=2, TIMEOUT=15 |
| HF Free tier + 上游响应慢(>30s) | MAX=2, TIMEOUT=30 |
| HF Pro tier + 上游响应快(<15s) | MAX=10, TIMEOUT=15 |
| HF Pro tier + 上游响应慢(>30s) | MAX=5, TIMEOUT=30 |
**关键原则**:
- `MAX_CONCURRENT_SOURCES` × `SOURCE_TIMEOUT_SECONDS` ≈ HF Worker 数 × 每个 worker 的时间
- Free tier:2 workers × 30s = 最多 60s 的并发时间
- Pro tier:10 workers × 15s = 最多 150s 的并发时间
## 监控和报警
### 指标
1. **pool_stats.active** - 当前活跃请求数(>max_concurrent 时饱和)
2. **pool_stats.queued** - 排队请求数(>0 说明有排队)
3. **pool_stats.timeout** - 超时请求数(持续增加说明上游响应慢)
4. **pool_stats.failed** - 失败请求数(>0 需要排查)
### 报警规则
- `active >= max_concurrent` 持续 5 分钟 → 考虑增加 `MAX_CONCURRENT_SOURCES`
- `timeout > 10` 持续 5 分钟 → 考虑增加 `SOURCE_TIMEOUT_SECONDS`
- `queued > 0` 持续 10 分钟 → 考虑优化上游或增加 worker
## 参考资料
- HuggingFace Spaces Worker Pool: https://huggingface.co/docs/hub/en/spaces-overview
- FastAPI Concurrent Requests: https://fastapi.tiangolo.com/async/
- Python ThreadPoolExecutor: https://docs.python.org/3/library/concurrent.futures.html
|