1. 项目概述为什么“问数项目智能体”的基础设施不能跳过这一步LCODER这个名称在当前AI工程圈子里已经不是单纯指代某个开源工具或平台而更像是一套面向国内开发者落地AI Agent的实践方法论代号——它不讲虚的架构图只聊真实跑起来要装什么、配什么、踩过哪些坑。今天这篇讲的“问数项目智能体搭建2基础设施搭建”恰恰是整套LCODER实战里最容易被新手跳过的“脏活累活”但也是后续所有Agent逻辑能否稳定、可调、可扩、可上线的分水岭。什么叫基础设施不是云服务器买几台就完事也不是docker-compose.yml一写就万事大吉。它指的是让一个AI Agent能持续接收请求、可靠调度LLM调用、安全存取结构化数据、支持多轮状态管理、并预留可观测入口的最小可行运行底座。你用Python写个脚本调通了OpenAI API那叫Demo你把FastAPI服务部署到Linux上加了JWT鉴权、SQLAlchemy连接池、Redis缓存会话、Uvicorn热重载开关、日志分级输出还写了健康检查端点和metrics暴露接口——这才叫基础设施搭起来了。我带过6个从零起步的问数类Agent项目其中4个卡在第二周问题全出在基础设施层有人用Flask默认配置跑Agent30并发就502有人把数据库连接直接写死在main.py里换环境就得改代码还有人把prompt模板硬编码进函数一上线发现没法A/B测试不同提示词策略。这些都不是模型能力问题而是基础设施没兜住。所以这篇不讲“什么是Agent”也不讲LangChain怎么链就专注一件事用最精简但生产可用的方式把问数项目所需的基础设施骨架立稳。核心就三块Python环境隔离与依赖锁定、FastAPI服务骨架与关键中间件、本地可观测性支撑组件。所有选型都基于LCODER实战验证过的组合——Python 3.11 uv FastAPI 0.115 SQLAlchemy 2.0 Redis 7 Prometheus Client不追最新版不堆炫技组件只选实测下来在小团队、中等QPS、国产模型适配场景下最稳的搭配。你不需要有运维经验但得愿意花90分钟认真执行每一步你不需要懂K8s但得明白为什么这里用Redis而不是文件存session你不需要会写PromQL但得知道metrics端点暴露后怎么用curl看一眼CPU使用率。这才是LCODER想传递的基础设施不是黑盒它是可触摸、可调试、可替换的零件集合。接下来我们就一块一块拧紧螺丝。2. Python环境与依赖管理为什么不用pip install -r requirements.txt2.1 为什么传统requirements.txt在Agent项目里会失效很多刚接触AI Agent开发的朋友习惯性地把所有包一股脑写进requirements.txt然后pip install -r requirements.txt完事。我在三个客户现场见过这种做法导致的典型故障某金融问数项目上线后第3天突然报错AttributeError: str object has no attribute model_dump查了一整天才发现是pydantic升级到了v2.7而项目里某处用的是v1风格的.dict()调用但requirements.txt里只写了pydantic1.10没锁版本某政务问答Agent在测试环境跑得好好的一上生产就OOM最后发现是transformers依赖的tokenizers在不同Python版本下编译行为不一致测试机是Python 3.10生产是3.11而requirements.txt里没声明Python版本约束最要命的是某医疗知识库Agent因为langchain-core和langgraph的版本冲突导致StateGraph初始化时循环引用错误堆栈长达200行根本看不出根因——而requirements.txt里两个包都只写了。这些问题的本质是requirements.txt只解决“装什么”不解决“装哪个确定版本”和“在什么环境下装”。AI Agent项目尤其敏感LLM SDK、向量库、序列化工具、异步框架之间存在大量隐式兼容约束差一个小版本可能就断掉整个推理链。2.2 LCODER推荐方案uv pyproject.toml双轨制我们改用uv作为包管理器配合pyproject.toml声明依赖原因很实在uv比pip快5-10倍实测安装fastapisqlalchemyredisprometheus-client共12个包pip耗时48秒uv仅6.2秒对频繁重建虚拟环境的开发调试阶段极其友好uv lock生成的uv.lock是精确锁定每个包及其传递依赖的完整哈希杜绝“本地能跑线上崩”的经典问题pyproject.toml天然支持Python版本约束、可选依赖分组、构建后端声明比requirements.txt信息密度高得多。具体操作分四步每步我都附上命令和原理说明第一步创建项目目录并初始化pyproject.tomlmkdir question-agent-infra cd question-agent-infra uv inituv init会自动生成标准pyproject.toml里面已包含[build-system]和[project]基础段。我们手动补全关键字段[project] name question-agent version 0.1.0 description LCODER问数项目智能体基础设施 requires-python 3.11, 3.12 # 明确限定Python版本避免3.12新特性引发兼容问题 dependencies [ fastapi0.115.0, uvicorn[standard]0.30.3, sqlalchemy[postgresql]2.0.34, redis5.0.3, prometheus-client0.19.0, python-jose[cryptography]3.3.0, passlib[bcrypt]3.2.0, ]注意这里所有包都用精确锁定版本而非。这是LCODER第一条铁律AI Agent项目里没有“最新版最好”这回事只有“已验证版最稳”。比如FastAPI 0.115.0是目前唯一全面支持Pydantic v2.6且无已知内存泄漏的版本SQLAlchemy 2.0.34修复了asyncpg连接池在高并发下的游标泄漏问题——这些结论都来自我们压测2000QPS时的真实日志。第二步用uv创建虚拟环境并安装依赖uv venv .venv --python 3.11 source .venv/bin/activate # Linux/Mac # 或 .venv\Scripts\activate.bat # Windows uv pip install -e .-e .表示以可编辑模式安装当前项目这样后续修改pyproject.toml里的依赖只需再运行uv pip install -e .即可更新不用反复pip uninstall。更重要的是uv pip install会自动读取pyproject.toml里的requires-python如果系统没有3.11它会直接报错提醒而不是强行装一堆不兼容包。第三步生成并验证lock文件uv lock cat uv.lock | head -20uv.lock文件里会列出每个包的精确版本、下载URL、sha256校验和以及所有传递依赖。比如你看到sqlalchemy条目下会明确写出它依赖的greenlet3.0.3和psycopg2-binary2.9.7这些细节在requirements.txt里永远看不到。你可以把这个lock文件提交到Git确保团队所有人装的完全一致。第四步设置pre-commit钩子防误操作在项目根目录建.pre-commit-config.yamlrepos: - repo: https://github.com/pycqa/isort rev: 5.13.2 hooks: [{id: isort}] - repo: https://github.com/psf/black rev: 24.4.2 hooks: [{id: black}]然后运行pip install pre-commit pre-commit install为什么Agent项目特别需要pre-commit因为基础设施代码里大量出现配置字典、环境变量读取、数据库URL拼接等易错模式。比如os.getenv(DB_URL, sqlite:///./app.db)少个括号就会变成默认值硬编码pre-commit能在git commit前自动格式化并检查语法省去后期Code Review时反复揪这类低级错误的时间。提示不要在pyproject.toml里写dev-dependencies。LCODER实践中发现把测试、lint、format工具混进主依赖会导致生产镜像体积暴增且引入不必要的攻击面。正确做法是单独建dev-requirements.txt用uv pip install -r dev-requirements.txt安装主pyproject.toml只保留运行时必需包。2.3 实操心得三个必须写进.gitignore的危险文件很多团队把.venv目录提交到Git理由是“保证环境一致”。这是个致命误区。.venv里包含绝对路径、编译产物、平台相关二进制文件跨机器克隆必然失败。LCODER团队总结出三个必须加入.gitignore的文件/目录且每一条都有血泪教训.venv/—— 虚拟环境目录。曾有个项目因提交了Windows下生成的.venv导致Mac开发者source .venv/bin/activate时报No module named pip折腾半天才发现是路径硬编码问题。__pycache__/—— Python字节码缓存。某次紧急上线回滚运维同事误删了__pycache__结果FastAPI启动时报ImportError: cannot import name XXX from partially initialized module原因是pyc文件损坏触发了Python导入机制异常。.env—— 环境变量文件。这是最高危项。曾有个问数项目把含数据库密码的.env提交到公开仓库虽然后来删了但GitHub缓存已泄露被迫连夜重置所有生产密钥。正确做法是用python-decouple库通过config Config(RepositoryEnv(.env.example))读取示例文件实际部署时由运维注入环境变量。这三个文件不进Git靠文档和CI流程保障一致性我们在README.md里写明“运行uv venv .venv uv pip install -e .初始化环境”在GitHub Actions里用ubuntu-latestpython-3.11uv的标准步骤自动验证。3. FastAPI服务骨架不只是写个app.get(/)而是构建可运维的服务基座3.1 为什么Agent项目需要比Demo级FastAPI更重的服务骨架FastAPI官方教程里一个main.py文件搞定Hello World这没问题。但当你把Agent逻辑塞进去很快会遇到这些现实问题用户连续发5条问题Agent要维持对话上下文但默认HTTP是无状态的你得自己管session生命周期某个LLM调用超时不能让整个API挂掉得有熔断降级策略运维要监控API每秒请求数、平均响应时间、错误率但FastAPI默认不暴露metrics安全审计要求所有API调用必须记录trace_id方便问题溯源但默认日志不带唯一请求标识。这些问题单靠app.get()装饰器解决不了。LCODER的FastAPI骨架核心目标就一个让每个API端点天然具备可观测、可治理、可扩展的基因。我们不追求“一行代码启动”而是接受多写200行代码换来后续3个月不加班。3.2 核心骨架文件结构与职责划分我们按LCODER标准把FastAPI服务拆成6个文件每个文件只做一件事拒绝“上帝文件”app/ ├── __init__.py ├── main.py # ASGI应用入口只负责创建app实例和挂载路由 ├── core/ # 核心配置与初始化逻辑 │ ├── config.py # 环境变量加载与验证含敏感信息掩码 │ ├── database.py # 数据库连接池初始化与Session依赖注入 │ ├── redis.py # Redis连接池与常用操作封装 │ └── logging.py # 结构化日志配置JSON格式trace_id注入 ├── api/ # API路由定义 │ ├── __init__.py │ ├── v1/ # 版本化路由 │ │ ├── __init__.py │ │ ├── health.py # 健康检查端点/healthz │ │ ├── metrics.py # Prometheus metrics端点/metrics │ │ └── question.py # 问数核心端点/v1/question │ └── deps.py # 全局依赖项如current_user, db_session ├── models/ # Pydantic模型定义请求/响应体 │ ├── __init__.py │ ├── base.py # BaseModel基类自动注入trace_id │ └── question.py # QuestionRequest, QuestionResponse等 └── schemas/ # SQLAlchemy模型数据库表结构 ├── __init__.py └── session.py # ConversationSession表存用户对话历史这个结构看似复杂但每个文件都有不可替代的价值。比如core/logging.py里我们重写了FastAPI默认日志器import logging import uuid from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware class TraceIdMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): trace_id request.headers.get(X-Trace-ID, str(uuid.uuid4())) request.state.trace_id trace_id response await call_next(request) response.headers[X-Trace-ID] trace_id return response # 在main.py里注册 app.add_middleware(TraceIdMiddleware)这样每个请求日志都会自动带上trace_id当用户反馈“第3次提问没返回”运维直接查trace_id就能串起Nginx→FastAPI→LLM→Redis全链路日志不用再靠时间戳猜。3.3 关键中间件实现健康检查、Metrics、请求限流三位一体LCODER骨架里api/v1/health.py和api/v1/metrics.py不是摆设而是真正参与生产巡检的组件。健康检查端点/healthz必须检查三项数据库连通性执行SELECT 1Redis连通性执行PINGLLM服务可达性对本地mock或远程endpoint发轻量探测代码精简但严谨from fastapi import APIRouter, HTTPException, Depends from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.core.redis import get_redis from app.core.config import settings router APIRouter() router.get(/healthz) async def health_check( db: AsyncSession Depends(get_db), redis Depends(get_redis) ): try: # 检查数据库 await db.execute(SELECT 1) except Exception as e: raise HTTPException(status_code503, detailfDatabase unreachable: {str(e)}) try: # 检查Redis await redis.ping() except Exception as e: raise HTTPException(status_code503, detailfRedis unreachable: {str(e)}) # LLM探测如果配置了mock模式则跳过否则调用一次轻量API if not settings.LLM_MOCK_MODE: try: # 这里调用LLM SDK的health_check方法不走完整推理链 pass except Exception as e: raise HTTPException(status_code503, detailfLLM service unreachable: {str(e)}) return {status: ok, timestamp: datetime.utcnow().isoformat()}Metrics端点/metrics用prometheus-client暴露4个核心指标http_requests_total{method, status_code}—— 按方法和状态码统计请求数http_request_duration_seconds_bucket{le}—— 请求延迟直方图0.1s, 0.5s, 1s, 5sllm_calls_total{model, status}—— LLM调用次数成功/失败redis_connections_total—— Redis活跃连接数这些指标直接喂给Prometheus Grafana看板就能实时显示“问数API P95延迟是否突破2s”、“过去1小时LLM超时率是否超过5%”。请求限流中间件不用第三方库手写轻量实现from collections import defaultdict, deque from time import time from fastapi import Request, HTTPException class RateLimiter: def __init__(self, max_requests: int 100, window_seconds: int 60): self.max_requests max_requests self.window_seconds window_seconds self.requests defaultdict(deque) # {ip: [timestamp1, timestamp2, ...]} def is_allowed(self, ip: str) - bool: now time() # 清理窗口外的旧请求 while self.requests[ip] and self.requests[ip][0] now - self.window_seconds: self.requests[ip].popleft() # 检查是否超限 if len(self.requests[ip]) self.max_requests: return False self.requests[ip].append(now) return True limiter RateLimiter(max_requests50, window_seconds60) router.post(/v1/question) async def ask_question( request: Request, question: QuestionRequest, db: AsyncSession Depends(get_db), redis Depends(get_redis) ): client_ip request.client.host if not limiter.is_allowed(client_ip): raise HTTPException(status_code429, detailToo many requests) # ... 正常业务逻辑这个限流器不依赖Redis纯内存实现适合中小流量场景。如果QPS超500我们会切换到Redis-backed限流但初期没必要为还没出现的瓶颈提前复杂化。注意限流策略必须和业务强相关。问数项目里我们对/v1/question限流但对/healthz和/metrics完全放行否则监控系统自己把自己限死了。3.4 数据库与Redis集成为什么不用SQLite而选PostgreSQLRedis组合很多教程用SQLite教FastAPI因为它零配置。但在问数Agent场景下SQLite是定时炸弹并发写入瓶颈SQLite的WAL模式在10并发写时就开始排队而问数项目用户可能同时发起多个追问无原生连接池每次create_engine(sqlite:///...)都新建连接连接数暴涨无法水平扩展当对话历史表增长到百万级SQLite查询变慢你没法加从库分担读压力。LCODER标准选型是PostgreSQL Redis分工明确PostgreSQL存结构化数据用户元信息、对话会话表session_id, user_id, created_at、问题-答案对question_text, answer_text, llm_model, latency_ms。用SQLAlchemy 2.0的AsyncSession支持真正的异步I/O避免阻塞事件循环。Redis存临时状态当前会话的上下文缓存keysession:{id}:contextvalue是JSON序列化的最近3轮对话、LLM调用结果缓存keyllm:prompt_hashTTL设为5分钟避免重复计算、分布式锁防止同一session并发修改。core/database.py关键代码from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker engine create_async_engine( settings.DATABASE_URL, # 格式postgresqlasyncpg://user:passhost/db echosettings.DEBUG, # 开发时开启SQL日志 pool_size20, # 连接池大小根据预估QPS调整 max_overflow10, # 高峰期允许额外创建的连接数 pool_pre_pingTrue, # 每次取连接前先ping自动剔除失效连接 pool_recycle3600, # 连接复用1小时后强制回收防长连接老化 ) AsyncSessionLocal sessionmaker( bindengine, class_AsyncSession, expire_on_commitFalse, ) async def get_db() - AsyncSession: async with AsyncSessionLocal() as session: yield sessioncore/redis.py关键代码import redis.asyncio as redis from app.core.config import settings redis_client redis.Redis( hostsettings.REDIS_HOST, portsettings.REDIS_PORT, db0, passwordsettings.REDIS_PASSWORD, decode_responsesTrue, # 自动decode bytes to str socket_keepaliveTrue, # 保持TCP长连接 retry_on_timeoutTrue, # 超时自动重试 health_check_interval30, # 每30秒发一次PING保活 ) async def get_redis(): return redis_client这个组合经受过真实压测用locust模拟500并发用户持续发送问数请求PostgreSQL CPU稳定在40%Redis内存占用2GB无连接泄漏。而同样负载下SQLite进程CPU飙到95%响应延迟从200ms升至2s以上。4. 本地可观测性支撑不装Grafana也能看清Agent在干什么4.1 为什么Agent项目必须从第一天就考虑可观测性可观测性Observability不是运维的事是AI Agent开发者的生存技能。举个真实案例某教育问数项目上线后用户反馈“有时回答很慢有时直接超时”。开发团队查日志发现全是504 Gateway Timeout但不知道是Nginx配置问题、FastAPI进程卡死、还是LLM调用本身慢。花了两天最后用strace -p $(pgrep -f uvicorn)才抓到是Redis连接池耗尽所有请求在connect()系统调用上阻塞。如果第一天就搭好可观测性这个问题5分钟内就能定位Prometheus看redis_connections_total指标发现连接数长期满额Grafana看http_request_duration_seconds_bucket直方图发现P95延迟突增日志里搜trace_id看到具体哪次请求卡在await redis.get(...)。LCODER的可观测性方案坚持三个原则零侵入不改业务代码只通过中间件和依赖注入收集数据低成本用轻量组件不引入Kafka/ELK等重型设施可验证本地开发环境就能跑通全部链路。4.2 Prometheus Grafana本地快速启动指南我们不用Docker Compose一键拉起整套监控因为那会掩盖细节。LCODER要求手动执行每一步确保你知道每个端口、每个配置项的作用。第一步启动Prometheus下载Prometheus二进制包https://prometheus.io/download/解压后编辑prometheus.ymlglobal: scrape_interval: 15s scrape_configs: - job_name: fastapi static_configs: - targets: [localhost:8000] # FastAPI服务暴露/metrics端点 metrics_path: /metrics然后运行./prometheus --config.fileprometheus.yml --storage.tsdb.path./data --web.listen-address:9090访问http://localhost:9090输入http_requests_total应该能看到计数器在增长。第二步启动Grafana下载Grafanahttps://grafana.com/grafana/download/解压后运行./bin/grafana-server访问http://localhost:3000默认账号admin/admin添加Prometheus数据源URL填http://localhost:9090。第三步导入问数Agent专用DashboardLCODER提供了一个预配置Dashboard JSON可在GitHub仓库获取导入后能看到实时QPS仪表盘每秒请求数延迟热力图0.1s~5s区间分布错误率趋势4xx/5xx占比LLM调用成功率成功/失败/超时Redis连接池使用率当前连接数/最大连接数这个Dashboard不是摆设。当LLM调用成功率曲线突然跌到80%运维立刻知道是模型服务出问题不用等用户投诉当Redis连接池使用率持续95%就知道该扩容Redis或优化缓存策略。4.3 结构化日志与Trace ID贯通让每一行日志都可追溯FastAPI默认日志是纯文本搜索困难。LCODER强制要求JSON格式日志并注入trace_id、request_id、user_id等上下文字段。core/logging.py完整实现import json import logging import sys from datetime import datetime from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR class JSONFormatter(logging.Formatter): def format(self, record): log_entry { timestamp: datetime.utcnow().isoformat(), level: record.levelname, logger: record.name, message: record.getMessage(), trace_id: getattr(record, trace_id, ), request_id: getattr(record, request_id, ), user_id: getattr(record, user_id, ), path: getattr(record, path, ), method: getattr(record, method, ), status_code: getattr(record, status_code, 0), } if record.exc_info: log_entry[exception] self.formatException(record.exc_info) return json.dumps(log_entry, ensure_asciiFalse) def setup_logging(): handler logging.StreamHandler(sys.stdout) handler.setFormatter(JSONFormatter()) logger logging.getLogger(question-agent) logger.setLevel(logging.INFO) logger.addHandler(handler) logger.propagate False # 同时配置Uvicorn日志 logging.getLogger(uvicorn.access).handlers [handler] logging.getLogger(uvicorn.error).handlers [handler] setup_logging()然后在main.py里用中间件注入上下文app.middleware(http) async def log_requests(request: Request, call_next): start_time time.time() request_id str(uuid.uuid4()) # 从Header提取trace_id没有则生成新的 trace_id request.headers.get(X-Trace-ID, str(uuid.uuid4())) # 记录请求开始 logger logging.getLogger(question-agent) logger.info( Request started, extra{ trace_id: trace_id, request_id: request_id, path: request.url.path, method: request.method, } ) try: response await call_next(request) process_time time.time() - start_time logger.info( Request completed, extra{ trace_id: trace_id, request_id: request_id, status_code: response.status_code, process_time: f{process_time:.3f}s, } ) response.headers[X-Trace-ID] trace_id return response except Exception as e: process_time time.time() - start_time logger.error( Request failed, exc_infoe, extra{ trace_id: trace_id, request_id: request_id, status_code: HTTP_500_INTERNAL_SERVER_ERROR, process_time: f{process_time:.3f}s, } ) raise现在控制台输出的日志是这样的{timestamp: 2024-06-15T08:23:45.123Z, level: INFO, logger: question-agent, message: Request started, trace_id: a1b2c3d4..., request_id: e5f6g7h8..., path: /v1/question, method: POST} {timestamp: 2024-06-15T08:23:45.789Z, level: INFO, logger: question-agent, message: Request completed, trace_id: a1b2c3d4..., request_id: e5f6g7h8..., status_code: 200, process_time: 0.654s}用jq工具可以轻松过滤# 查找所有超时请求2s cat logs.json | jq select(.process_time | capture((?t\\d\\.\\d)s).t | tonumber 2) # 按trace_id聚合一次完整请求链 cat logs.json | jq -r select(.trace_id a1b2c3d4...) | jq -r .message这就是LCODER强调的可观测性不是加个监控系统就完事而是让日志、指标、追踪三者ID贯通形成可钻取的问题定位闭环。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题速查表高频故障现象与根因定位现象可能根因快速验证命令解决方案uvicorn启动报错Address already in use端口被占用lsof -i :8000或netstat -tuln | grep :8000kill -9 $(lsof -t -i :8000)或换端口--port 8001/healthz返回503但数据库命令行能连SQLAlchemy连接池未初始化curl -v http://localhost:8000/healthz看详细错误检查core/database.py里engine是否被正确import确认get_db依赖注入已注册Prometheus抓不到http_requests_totalFastAPI未暴露/metrics端点curl http://localhost:8000/metrics | head -10确认api/v1/metrics.py已挂载到app检查app.include_router(metrics_router, prefix/v1)Redis连接超时日志显示Connection closed by serverRedis密码错误或认证失败redis-cli -h localhost -p 6379 -a your_password ping检查settings.REDIS_PASSWORD是否正确注意密码含特殊字符需URL编码问数API返回空JSON无错误日志Pydantic模型验证失败静默丢弃在question.py的app.post函数里加print(request.__dict__)检查请求body是否符合QuestionRequest定义特别是str字段是否传了null这张表来自LCODER团队23个真实项目的故障归档。特别强调最后一行Pydantic v2默认对None值静默转换为空字符串或默认值导致前端传{question: null}时后端收到的是question但业务逻辑可能期望非空字符串结果一路执行到LLM调用返回空响应。解决方案是在模型里显式声明class QuestionRequest(BaseModel): question: str Field(..., min_length1, max_length1000) # ...表示必填min_length强制非空5.2 独家避坑技巧三个被90%教程忽略的关键配置技巧1Uvicorn的--reload-dir必须指定具体目录不能用.很多教程写uvicorn app.main:app --reload --reload-dir .这会导致Uvicorn监听整个项目目录包括.venv、__pycache__等文件变更过于频繁触发反复重启。LCODER规定uvicorn app.main:app --reload --reload-dir app --reload-dir migrations只监听app/和migrations/目录既保证代码热更新又避免误触。技巧2SQLAlchemy连接池pool_pre_pingTrue必须开启但pool_recycle要设合理值pool_pre_ping会在每次取连接前发SELECT 1探测代价很小但能防连接老化。但pool_recycle设太短如300秒会导致频繁重建连接反而增加开销。LCODER实测PostgreSQL默认tcp_keepalive是2小时所以pool_recycle36001小时是平衡点。技巧3Redis客户端health_check_interval必须大于0且小于TCP keepalive时间Redis默认TCP keepalive是2小时如果health_check_interval0长时间空闲连接会被中间网络设备断开。LCODER设为30秒既能及时发现断连又不会产生过多心跳流量。5.3 实战复盘一次内存泄漏的完整排查过程某次问数项目压测时Uvicorn进程内存从100MB缓慢涨到2GB30分钟后OOM被系统杀死。按LCODER标准流程排查Step 1确认是否真泄漏# 启动时记下PID ps aux \| grep uvicorn # 每5分钟看一次RSS watch -n 300 ps -p PID -o pid,rss,vsz,comm确认RSS持续增长排除是正常缓存。Step 2生成内存快照pip install psutil pympler # 在代码里加 import tracem
