feat(gateway): 全局串行化(并发=1) + 记录入参/出参/耗时日志
并发模型从「每worker并发1 + 多worker并行」改为全局串行:同一时间 只处理1个请求,其余排队;多 worker 仅作热备(主 worker 坏了才用备机)。 接口4(face/features) 走豆包、不占 GPU,不纳入串行。 - pool: asyncio.Semaphore(max_global_concurrency=1) + acquire/release_global_slot (依赖单进程 uvicorn 部署,已在注释中标注) - forward: proxy_request 最外层 acquire 全局槽、try/finally 全路径释放; 入参 multipart 解析挂 request.state;原重试/故障转移逻辑抽到 _dispatch_with_retries - reqlog(新): 标量入参保留;图片(file/base64)存盘转URL,绝不内嵌base64; 出参递归摘要截断(landmarks/长串/大数组) - logging_middleware: RequestLogEntry 加 request_params/response_data, jsonl 全量记录;_load_from_logfile 同步映射防重启丢字段;get_stats recent 暴露 - /gateway-health 暴露 global_max/global_busy/global_waiting - config: dispatch 新增 max_global_concurrency / max_queue_wait_seconds - tests: test_reqlog + test_gateway_serialization(9 用例) 顺带提交此前未提交的网关统计(daily stats 接口)与耗时看板(api_timing_dashboard.html)。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from gateway.config import load_config
|
||||
from gateway.logging_middleware import (
|
||||
get_available_dates,
|
||||
get_daily_stats,
|
||||
get_stats,
|
||||
init_logging as _init_req_logging,
|
||||
request_logging_middleware,
|
||||
@@ -185,6 +187,9 @@ async def gateway_health():
|
||||
"workers_total": status["total"],
|
||||
"workers_healthy": status["healthy"],
|
||||
"workers_busy": status["busy"],
|
||||
"global_max": status.get("global_max", 1),
|
||||
"global_busy": status.get("global_busy", 0),
|
||||
"global_waiting": status.get("global_waiting", 0),
|
||||
}
|
||||
|
||||
|
||||
@@ -201,6 +206,7 @@ async def index():
|
||||
"version": "0.1.0",
|
||||
"docs": "/docs",
|
||||
"stats": "/admin/stats",
|
||||
"timing_dashboard": "/static/api_timing_dashboard.html",
|
||||
"integration_guide": "/static/integration.html",
|
||||
"test_pages": {
|
||||
"if1_measure": "/static/test_interface1.html",
|
||||
@@ -470,6 +476,18 @@ async def admin_stats_json():
|
||||
return get_stats()
|
||||
|
||||
|
||||
@app.get("/admin/stats/dates", include_in_schema=False)
|
||||
async def admin_stats_dates():
|
||||
"""返回日志中出现过的所有本地日期(供耗时看板日期选择器使用)。"""
|
||||
return {"dates": get_available_dates()}
|
||||
|
||||
|
||||
@app.get("/admin/stats/daily", include_in_schema=False)
|
||||
async def admin_stats_daily(date: str):
|
||||
"""按天统计各接口调用耗时(含多发型接口的单次换发型耗时拆解)。"""
|
||||
return get_daily_stats(date)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 代理路由
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -529,6 +547,7 @@ async def hair_grow_b(request: Request):
|
||||
|
||||
@app.post("/api/v1/face/features", tags=["人脸分析"])
|
||||
async def face_features(
|
||||
request: Request,
|
||||
image_file: Optional[UploadFile] = File(default=None, description="上传图片文件(JPG/PNG)"),
|
||||
image_url: Optional[str] = Form(default=None, description="图片 URL"),
|
||||
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带前缀)"),
|
||||
@@ -559,6 +578,26 @@ async def face_features(
|
||||
"request_id": f"gw-{_uuid.uuid4().hex[:8]}", "data": None,
|
||||
})
|
||||
|
||||
# 入参日志(接口4 不走 forward.py,自行记录;不获取全局串行槽,因不占 GPU)
|
||||
try:
|
||||
from gateway.config import get_config as _get_cfg
|
||||
from gateway.reqlog import save_image_bytes
|
||||
_cfg = _get_cfg()
|
||||
_params: dict = {}
|
||||
if image_file:
|
||||
_url = save_image_bytes(img_bytes, _cfg["static_dir"], _cfg["public_base_url"]) if img_bytes else None
|
||||
_params["image_file"] = {"_image": True, "source": "file",
|
||||
"size": len(img_bytes) if img_bytes else 0, "url": _url}
|
||||
elif image_base64:
|
||||
_url = save_image_bytes(img_bytes, _cfg["static_dir"], _cfg["public_base_url"]) if img_bytes else None
|
||||
_params["image_base64"] = {"_image": True, "source": "base64",
|
||||
"size": len(img_bytes) if img_bytes else 0, "url": _url}
|
||||
elif image_url:
|
||||
_params["image_url"] = image_url
|
||||
request.state.log_request_params = _params
|
||||
except Exception: # noqa: BLE001
|
||||
request.state.log_request_params = {"_parse_error": "iface4"}
|
||||
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from face_features import analyze_features
|
||||
|
||||
|
||||
Reference in New Issue
Block a user