fix: 分块大小减至10MB + 上传失败确认机制 + 重试前置检查
- CHUNK_SIZE 20MB→10MB,CHUNK_THRESHOLD 50MB→20MB,降低单次超时概率 - 新增 _query_uploaded_chunks 独立方法,connect timeout 10s→30s - 分块上传失败后查询 Edge 确认是否实际收到,避免响应丢失导致不必要重试 - 补全 uploaded_set.add(idx) 防止重复上传已成功分块 - _poll_once 中前置 retry_count 检查,超限任务直接标记 FAILED
This commit is contained in:
@@ -27,8 +27,8 @@ from .. import db_layer
|
|||||||
|
|
||||||
logger = setup_logger('fam-core.dispatcher')
|
logger = setup_logger('fam-core.dispatcher')
|
||||||
|
|
||||||
CHUNK_SIZE = 20 * 1024 * 1024 # 20MB per chunk
|
CHUNK_SIZE = 10 * 1024 * 1024 # 10MB per chunk (smaller = fewer timeouts)
|
||||||
CHUNK_THRESHOLD = 50 * 1024 * 1024 # files > 50MB use chunked upload
|
CHUNK_THRESHOLD = 20 * 1024 * 1024 # files > 20MB use chunked upload
|
||||||
MAX_CHUNK_RETRIES = 3
|
MAX_CHUNK_RETRIES = 3
|
||||||
|
|
||||||
|
|
||||||
@@ -145,11 +145,25 @@ class Dispatcher:
|
|||||||
logger.error(f"[task_id={task_id}] Edge 返回异常状态码: {resp.status_code}")
|
logger.error(f"[task_id={task_id}] Edge 返回异常状态码: {resp.status_code}")
|
||||||
self._schedule_retry(task)
|
self._schedule_retry(task)
|
||||||
|
|
||||||
|
def _query_uploaded_chunks(self, task_id):
|
||||||
|
"""查询 Edge 端已上传分块列表"""
|
||||||
|
try:
|
||||||
|
resp = requests.get(
|
||||||
|
self.chunks_query_url,
|
||||||
|
params={'task_id': task_id},
|
||||||
|
timeout=(30, 15)
|
||||||
|
)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
return set(resp.json().get('uploaded_chunks', []))
|
||||||
|
except requests.RequestException:
|
||||||
|
pass
|
||||||
|
return set()
|
||||||
|
|
||||||
def _dispatch_chunked(self, task, payload, video_path, file_size):
|
def _dispatch_chunked(self, task, payload, video_path, file_size):
|
||||||
"""大文件分块上传 + 断点续传
|
"""大文件分块上传 + 断点续传
|
||||||
|
|
||||||
1. 查询 Edge 端已上传分块(断点续传)
|
1. 查询 Edge 端已上传分块(断点续传)
|
||||||
2. 上传缺失分块(每块最多重试 3 次)
|
2. 上传缺失分块(每块最多重试 3 次,失败后查询 Edge 确认是否实际收到)
|
||||||
3. 全部分块上传后调用 /assemble 合并入队
|
3. 全部分块上传后调用 /assemble 合并入队
|
||||||
"""
|
"""
|
||||||
task_id = task['task_id']
|
task_id = task['task_id']
|
||||||
@@ -157,20 +171,9 @@ class Dispatcher:
|
|||||||
filename = os.path.basename(video_path)
|
filename = os.path.basename(video_path)
|
||||||
|
|
||||||
# 1. 查询已上传分块(断点续传)
|
# 1. 查询已上传分块(断点续传)
|
||||||
uploaded_set = set()
|
uploaded_set = self._query_uploaded_chunks(task_id)
|
||||||
try:
|
if uploaded_set:
|
||||||
resp = requests.get(
|
logger.info(f"[task_id={task_id}] 断点续传: 已有 {len(uploaded_set)}/{total_chunks} 块")
|
||||||
self.chunks_query_url,
|
|
||||||
params={'task_id': task_id},
|
|
||||||
timeout=(10, 15)
|
|
||||||
)
|
|
||||||
if resp.status_code == 200:
|
|
||||||
data = resp.json()
|
|
||||||
uploaded_set = set(data.get('uploaded_chunks', []))
|
|
||||||
if uploaded_set:
|
|
||||||
logger.info(f"[task_id={task_id}] 断点续传: 已有 {len(uploaded_set)}/{total_chunks} 块")
|
|
||||||
except requests.RequestException as e:
|
|
||||||
logger.warning(f"[task_id={task_id}] 查询已上传分块失败(将从头上传): {e}")
|
|
||||||
|
|
||||||
# 2. 上传缺失分块
|
# 2. 上传缺失分块
|
||||||
try:
|
try:
|
||||||
@@ -195,7 +198,7 @@ class Dispatcher:
|
|||||||
'filename': filename,
|
'filename': filename,
|
||||||
},
|
},
|
||||||
files={'chunk': (f'chunk_{idx}', io.BytesIO(chunk_data))},
|
files={'chunk': (f'chunk_{idx}', io.BytesIO(chunk_data))},
|
||||||
timeout=(10, 120)
|
timeout=(30, 120)
|
||||||
)
|
)
|
||||||
if cresp.status_code == 200:
|
if cresp.status_code == 200:
|
||||||
success = True
|
success = True
|
||||||
@@ -208,10 +211,16 @@ class Dispatcher:
|
|||||||
time.sleep(5 * (attempt + 1))
|
time.sleep(5 * (attempt + 1))
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.error(f"[task_id={task_id}] 分块 {idx} 超过最大重试次数,安排文件级重试")
|
uploaded_now = self._query_uploaded_chunks(task_id)
|
||||||
|
if idx in uploaded_now:
|
||||||
|
logger.info(f"[task_id={task_id}] 分块 {idx} 虽超时但 Edge 已收到,继续下一块")
|
||||||
|
uploaded_set.add(idx)
|
||||||
|
continue
|
||||||
|
logger.error(f"[task_id={task_id}] 分块 {idx} 确认未收到,安排文件级重试")
|
||||||
self._schedule_retry(task)
|
self._schedule_retry(task)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
uploaded_set.add(idx)
|
||||||
if (idx + 1) % 5 == 0 or idx == total_chunks - 1:
|
if (idx + 1) % 5 == 0 or idx == total_chunks - 1:
|
||||||
logger.info(f"[task_id={task_id}] 分块进度: {idx + 1}/{total_chunks}")
|
logger.info(f"[task_id={task_id}] 分块进度: {idx + 1}/{total_chunks}")
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
@@ -278,6 +287,14 @@ class Dispatcher:
|
|||||||
|
|
||||||
tasks = db_layer.get_pending_tasks(limit=1)
|
tasks = db_layer.get_pending_tasks(limit=1)
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
|
if task['retry_count'] >= task['max_retries']:
|
||||||
|
db_layer.update_task_status(
|
||||||
|
task['task_id'], 'FAILED',
|
||||||
|
error_message=f"超过最大重试次数 {task['max_retries']}",
|
||||||
|
failure_stage='callback'
|
||||||
|
)
|
||||||
|
logger.warning(f"[task_id={task['task_id']}] retry_count={task['retry_count']} >= max_retries={task['max_retries']},标记 FAILED")
|
||||||
|
continue
|
||||||
if self._should_retry(task):
|
if self._should_retry(task):
|
||||||
try:
|
try:
|
||||||
self._dispatch_one(task)
|
self._dispatch_one(task)
|
||||||
|
|||||||
Reference in New Issue
Block a user