微信小程序页面迁移校验之前 P5任务处理之前
This commit is contained in:
@@ -16,6 +16,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import platform
|
||||
import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
@@ -25,6 +26,11 @@ from ..schemas.tasks import TaskConfigSchema
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# CHANGE 2026-03-07 | 实例标识:用于多后端实例共享同一 DB 时的任务隔离
|
||||
# 背景:发现有另一台机器(宿主机 D 盘)的后端也在消费同一个 task_queue,
|
||||
# 导致任务被错误实例执行。通过 enqueued_by 列实现"谁入队谁消费"。
|
||||
_INSTANCE_ID = platform.node()
|
||||
|
||||
# 后台循环轮询间隔(秒)
|
||||
POLL_INTERVAL_SECONDS = 2
|
||||
|
||||
@@ -43,6 +49,7 @@ class QueuedTask:
|
||||
finished_at: Any = None
|
||||
exit_code: int | None = None
|
||||
error_message: str | None = None
|
||||
schedule_id: str | None = None
|
||||
|
||||
|
||||
class TaskQueue:
|
||||
@@ -56,12 +63,13 @@ class TaskQueue:
|
||||
# 入队
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def enqueue(self, config: TaskConfigSchema, site_id: int) -> str:
|
||||
def enqueue(self, config: TaskConfigSchema, site_id: int, schedule_id: str | None = None) -> str:
|
||||
"""将任务配置入队,自动分配 position。
|
||||
|
||||
Args:
|
||||
config: 任务配置
|
||||
site_id: 门店 ID(门店隔离)
|
||||
schedule_id: 关联的调度任务 ID(可选)
|
||||
|
||||
Returns:
|
||||
新创建的队列任务 ID(UUID 字符串)
|
||||
@@ -84,18 +92,19 @@ class TaskQueue:
|
||||
max_pos = cur.fetchone()[0]
|
||||
new_pos = max_pos + 1
|
||||
|
||||
# CHANGE 2026-03-07 | 写入 enqueued_by 实现多实例任务隔离
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO task_queue (id, site_id, config, status, position)
|
||||
VALUES (%s, %s, %s, 'pending', %s)
|
||||
INSERT INTO task_queue (id, site_id, config, status, position, schedule_id, enqueued_by)
|
||||
VALUES (%s, %s, %s, 'pending', %s, %s, %s)
|
||||
""",
|
||||
(task_id, site_id, json.dumps(config_json), new_pos),
|
||||
(task_id, site_id, json.dumps(config_json), new_pos, schedule_id, _INSTANCE_ID),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
logger.info("任务入队 [%s] site_id=%s position=%s", task_id, site_id, new_pos)
|
||||
logger.info("任务入队 [%s] site_id=%s position=%s schedule_id=%s", task_id, site_id, new_pos, schedule_id)
|
||||
return task_id
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -114,19 +123,21 @@ class TaskQueue:
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
# 选取 position 最小的 pending 任务并锁定
|
||||
# CHANGE 2026-03-07 | 只消费本实例入队的任务(enqueued_by 匹配)
|
||||
# 背景:多后端实例共享同一 DB 时,防止 A 实例消费 B 实例入队的任务
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, site_id, config, status, position,
|
||||
created_at, started_at, finished_at,
|
||||
exit_code, error_message
|
||||
exit_code, error_message, schedule_id
|
||||
FROM task_queue
|
||||
WHERE site_id = %s AND status = 'pending'
|
||||
AND (enqueued_by = %s OR enqueued_by IS NULL)
|
||||
ORDER BY position ASC
|
||||
LIMIT 1
|
||||
FOR UPDATE SKIP LOCKED
|
||||
""",
|
||||
(site_id,),
|
||||
(site_id, _INSTANCE_ID),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
@@ -144,6 +155,7 @@ class TaskQueue:
|
||||
finished_at=row[7],
|
||||
exit_code=row[8],
|
||||
error_message=row[9],
|
||||
schedule_id=str(row[10]) if row[10] else None,
|
||||
)
|
||||
|
||||
# 更新状态为 running
|
||||
@@ -261,10 +273,11 @@ class TaskQueue:
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def list_pending(self, site_id: int) -> list[QueuedTask]:
|
||||
"""列出指定门店的所有 pending 任务,按 position 升序。"""
|
||||
"""列出指定门店的所有 pending 任务(仅限本实例入队的),按 position 升序。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
# CHANGE 2026-03-07 | 只列出本实例入队的 pending 任务
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT id, site_id, config, status, position,
|
||||
@@ -272,9 +285,10 @@ class TaskQueue:
|
||||
exit_code, error_message
|
||||
FROM task_queue
|
||||
WHERE site_id = %s AND status = 'pending'
|
||||
AND (enqueued_by = %s OR enqueued_by IS NULL)
|
||||
ORDER BY position ASC
|
||||
""",
|
||||
(site_id,),
|
||||
(site_id, _INSTANCE_ID),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
conn.commit()
|
||||
@@ -298,18 +312,20 @@ class TaskQueue:
|
||||
]
|
||||
|
||||
def has_running(self, site_id: int) -> bool:
|
||||
"""检查指定门店是否有 running 状态的任务。"""
|
||||
"""检查指定门店是否有本实例的 running 状态任务。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
# CHANGE 2026-03-07 | 只检查本实例的 running 任务
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM task_queue
|
||||
WHERE site_id = %s AND status = 'running'
|
||||
AND (enqueued_by = %s OR enqueued_by IS NULL)
|
||||
)
|
||||
""",
|
||||
(site_id,),
|
||||
(site_id, _INSTANCE_ID),
|
||||
)
|
||||
result = cur.fetchone()[0]
|
||||
conn.commit()
|
||||
@@ -333,7 +349,10 @@ class TaskQueue:
|
||||
from .task_executor import task_executor
|
||||
|
||||
self._running = True
|
||||
logger.info("TaskQueue process_loop 启动")
|
||||
logger.info(
|
||||
"TaskQueue process_loop 启动 (instance_id=%s,仅消费本实例入队的任务)",
|
||||
_INSTANCE_ID,
|
||||
)
|
||||
|
||||
while self._running:
|
||||
try:
|
||||
@@ -369,6 +388,7 @@ class TaskQueue:
|
||||
asyncio.create_task(
|
||||
self._execute_and_update(
|
||||
executor, config, execution_id, task.id, site_id,
|
||||
schedule_id=task.schedule_id,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -379,6 +399,7 @@ class TaskQueue:
|
||||
execution_id: str,
|
||||
queue_id: str,
|
||||
site_id: int,
|
||||
schedule_id: str | None = None,
|
||||
) -> None:
|
||||
"""执行任务并更新队列状态。"""
|
||||
try:
|
||||
@@ -387,6 +408,7 @@ class TaskQueue:
|
||||
execution_id=execution_id,
|
||||
queue_id=queue_id,
|
||||
site_id=site_id,
|
||||
schedule_id=schedule_id,
|
||||
)
|
||||
# 执行完成后根据 executor 的结果更新 task_queue 状态
|
||||
self._update_queue_status_from_log(queue_id)
|
||||
@@ -395,15 +417,18 @@ class TaskQueue:
|
||||
self._mark_failed(queue_id, "执行过程中发生未捕获异常")
|
||||
|
||||
def _get_pending_site_ids(self) -> list[int]:
|
||||
"""获取所有有 pending 任务的 site_id 列表。"""
|
||||
"""获取所有有 pending 任务的 site_id 列表(仅限本实例入队的)。"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
# CHANGE 2026-03-07 | 只查本实例入队的 pending 任务
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT DISTINCT site_id FROM task_queue
|
||||
WHERE status = 'pending'
|
||||
"""
|
||||
AND (enqueued_by = %s OR enqueued_by IS NULL)
|
||||
""",
|
||||
(_INSTANCE_ID,),
|
||||
)
|
||||
rows = cur.fetchall()
|
||||
conn.commit()
|
||||
|
||||
Reference in New Issue
Block a user