优化架构K8s 副本和队列

This commit is contained in:
Azmat@qq.com
2026-09-03 16:38:02 +08:00
parent f53b6e1035
commit e7122b78fb
9 changed files with 80 additions and 10 deletions
+8
View File
@@ -204,6 +204,14 @@ CELERY_TASK_ACKS_LATE = True
CELERY_TASK_REJECT_ON_WORKER_LOST = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
CELERY_TIMEZONE = TIME_ZONE
# 快活走 airshelf.quick(独立 worker-quick Pod);出图/出片仍默认 celery 队列。
CELERY_TASK_ROUTES = {
"apps.ai.tasks.extract_entities_task": {"queue": "airshelf.quick"},
"apps.ai.tasks.poll_free_video_task": {"queue": "airshelf.quick"},
"apps.ai.tasks.drain_asset_reviews_task": {"queue": "airshelf.quick"},
"apps.ops.tasks.ensure_team_notifications_task": {"queue": "airshelf.quick"},
"apps.projects.tasks.run_quick_script_task": {"queue": "airshelf.quick"},
}
REDIS_LOCK_URL = env("REDIS_LOCK_URL", "redis://127.0.0.1:6379/3")
+5 -3
View File
@@ -1,5 +1,7 @@
from airshelf.celery import app
QUICK_QUEUE = "airshelf.quick"
@app.task(bind=True, max_retries=3)
def submit_ai_task(self, task_id: str) -> str:
@@ -21,7 +23,7 @@ def generate_standalone_image_task(self, task_id: str) -> str:
return task_id
@app.task(bind=True, max_retries=0)
@app.task(bind=True, max_retries=0, queue=QUICK_QUEUE)
def extract_entities_task(self, task_id: str) -> str:
"""实体提取的慢活(豆包思考模型流式,可达数十秒)在 worker 内跑,Web 层秒回不被占住、不再 502。
幂等且失败自退费(见 run_extract_entities_task),故 max_retries=0,不向上抛重试。"""
@@ -83,7 +85,7 @@ def generate_model_triview_task(self, task_id: str) -> str:
return task_id
@app.task(bind=True, max_retries=0)
@app.task(bind=True, max_retries=0, queue=QUICK_QUEUE)
def poll_free_video_task(self, task_id: str, attempt: int = 0) -> str:
"""自由创作视频·worker 兜底轮询:每 30s 一次自重排(不依赖 celery beat),
最多 60 次(约 30 分钟);用尽后只停止 Worker 兜底,不把业务任务判失败。
@@ -119,7 +121,7 @@ def poll_free_video_task(self, task_id: str, attempt: int = 0) -> str:
return task_id
@app.task(bind=True, max_retries=0)
@app.task(bind=True, max_retries=0, queue=QUICK_QUEUE)
def drain_asset_reviews_task(self) -> str:
"""全平台人脸审核兜底:未送审自动送火山、审核中自动拉绿/红盾。
自重排(无 celery beat);eager 下不自转,避免单测死循环。"""
+1 -1
View File
@@ -4,7 +4,7 @@ from airshelf.celery import app
from apps.accounts.models import Team
@app.task
@app.task(queue="airshelf.quick")
def ensure_team_notifications_task(team_id: str, user_id: str | None = None) -> None:
"""后台生成/补齐团队站内通知。
+7 -1
View File
@@ -453,7 +453,13 @@ export function App() {
}
const active = detail.video_segments.filter((segment) => ["running", "queued"].includes(segment.status));
if (active.length === 0) return;
await Promise.all(active.map((segment) => api.pollVideo(activeProjectId, segment.id).catch(() => undefined)));
const polls = await Promise.all(active.map((segment) => api.pollVideo(activeProjectId, segment.id).catch(() => undefined)));
// 仍在 queued/running 就别拉整棵项目树(26KB);只有某段终态才回读详情。
const settled = polls.some((row) => {
const status = row && typeof row === "object" ? (row as { status?: string }).status : "";
return Boolean(status) && status !== "running" && status !== "queued";
}) || polls.some((row) => row && typeof row === "object" && "id" in (row as object));
if (!settled) return;
const next = await api.project(activeProjectId).catch(() => null);
// 晚到的旧项目轮询结果不要冲掉已切换的当前项目详情
if (next && next.id === activeProjectIdRef.current) {
+11 -2
View File
@@ -2668,8 +2668,17 @@ export function PipelinePage(props: {
const activeVideoCount = segments.filter((s) => ["running", "queued"].includes(s.status)).length;
useEffect(() => {
if (activeVideoCount === 0) return;
const timer = window.setInterval(() => { void onPollVideosQuiet(); }, 5000);
return () => window.clearInterval(timer);
const tick = () => {
if (document.hidden) return;
void onPollVideosQuiet();
};
const timer = window.setInterval(tick, 5000);
const onVis = () => { if (!document.hidden) void onPollVideosQuiet(); };
document.addEventListener("visibilitychange", onVis);
return () => {
window.clearInterval(timer);
document.removeEventListener("visibilitychange", onVis);
};
}, [activeVideoCount, onPollVideosQuiet]);
// 进入视频 / 拼接阶段时回填已有成片(此前合成过就直接给出播放/下载入口)