feat(gateway): 接口5排队插队优先级,两级队列均支持
- 全局槽换为 _PrioritySemaphore:priority 大者排到等待队列最前,同级 FIFO, 无等待者时才允许直接获取(收紧 asyncio.Semaphore 的 barging); 授予槽位瞬间请求被取消时归还槽位,防泄漏 - worker 池 Condition FIFO 等待改为显式优先级等待队列,release 唤醒队首; 被唤醒但 worker 被抢时按原序号重排;授予/取消竞态时转手唤醒下一位 - forward.py 新增 PRIORITY_PATHS(现仅接口5 /api/v1/hairline/generate) - 新增 4 个单测:两级队列的插队 + 同级 FIFO
This commit is contained in:
+17
-6
@@ -29,6 +29,15 @@ from gateway.reqlog import extract_form_params
|
||||
|
||||
logger = logging.getLogger("gateway.forward")
|
||||
|
||||
# 插队优先级接口:排队(全局槽 / worker 池)时排到等待队列最前,同优先级 FIFO。
|
||||
PRIORITY_PATHS = frozenset({
|
||||
"/api/v1/hairline/generate", # 接口5:发际线PNG生成
|
||||
})
|
||||
|
||||
|
||||
def _priority_of(path: str) -> int:
|
||||
return 1 if path in PRIORITY_PATHS else 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 全局 httpx client(连接复用)
|
||||
@@ -137,7 +146,8 @@ async def proxy_request(request: Request, path: str) -> JSONResponse:
|
||||
"""代理一次请求到 worker,处理重试与 base64 改写。
|
||||
|
||||
流程:
|
||||
1. acquire worker(排队等待空闲)
|
||||
0. 获取全局串行槽(PRIORITY_PATHS 中的接口插队到最前)
|
||||
1. acquire worker(排队等待空闲,同样支持优先级插队)
|
||||
2. 重构 multipart/form 请求,加 X-Internal-Token
|
||||
3. 转发到 worker
|
||||
4. 成功 → release worker → 改写 base64 → 返回 JSONResponse
|
||||
@@ -161,8 +171,9 @@ async def proxy_request(request: Request, path: str) -> JSONResponse:
|
||||
except Exception as ex: # noqa: BLE001
|
||||
request.state.log_request_params = {"_parse_error": str(ex)}
|
||||
|
||||
# --- 全局串行槽(GPU 请求在此排队;超时拒绝) ---
|
||||
if not await acquire_global_slot(cfg):
|
||||
# --- 全局串行槽(GPU 请求在此排队;超时拒绝;优先级接口插队到最前) ---
|
||||
priority = _priority_of(path)
|
||||
if not await acquire_global_slot(cfg, priority):
|
||||
request.state.worker_url = ""
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
@@ -175,13 +186,13 @@ async def proxy_request(request: Request, path: str) -> JSONResponse:
|
||||
)
|
||||
|
||||
try:
|
||||
return await _dispatch_with_retries(request, path, cfg, body)
|
||||
return await _dispatch_with_retries(request, path, cfg, body, priority)
|
||||
finally:
|
||||
release_global_slot()
|
||||
|
||||
|
||||
async def _dispatch_with_retries(
|
||||
request: Request, path: str, cfg: dict, body: bytes
|
||||
request: Request, path: str, cfg: dict, body: bytes, priority: int = 0
|
||||
) -> JSONResponse:
|
||||
"""在已持有全局串行槽的前提下:选 worker → 转发 → 重试 → 改写 base64 → 返回。
|
||||
|
||||
@@ -203,7 +214,7 @@ async def _dispatch_with_retries(
|
||||
for attempt in range(attempts):
|
||||
worker = None
|
||||
try:
|
||||
worker = await acquire_worker(cfg)
|
||||
worker = await acquire_worker(cfg, priority)
|
||||
# 记录当前使用的 worker,供日志中间件读取
|
||||
request.state.worker_url = worker.url
|
||||
except NoWorkerAvailable:
|
||||
|
||||
Reference in New Issue
Block a user