Hermes Agent:连接池参数怎么配,4 个指标验证调优生效
凌晨 3 点告警:连接池打满,接口 P99 飙到 8 秒,日志刷满PoolTimeout。别急着拉大数字——Hermes Agent 的出站连接池和会话状态库是两处独立配置,这份指南直接给你能跑的参数和验证命令。
🔧 一份能直接跑的配置长什么样
连接池(预先开好一批连接反复用,省掉每次请求的握手开销)在 Hermes Agent 里分两块:出站 HTTP 池和 SQLite 会话库。下面这份配置摘录里,outbound段的值对应 agent/process_bootstrap.py 中httpx.Limits/httpx.Timeout的默认常量,state段对应 acp_adapter/session.py 初始化state.db的参数。
outbound: # LLM 提供商请求的出站连接池 pool: max_keepalive_connections: 20 # 空闲连接复用上限,多出来的空闲连接直接关闭 max_connections: 100 # 并发出站连接硬上限,超过的新请求排队等待 keepalive_expiry: 20.0 # 空闲连接回收秒数,必须小于反向代理的 30-60s 超时 timeout: connect: 15.0 # TCP 建连超时(秒),超时说明是网络层问题 read: null # SSE 流式场景不设读超时,别动 write: 15.0 pool: 10.0 # 从池里取连接的最长等待(秒),超时即 PoolTimeout state: # 会话状态存储,SQLite db: path: state.db # 位于 Hermes 主目录下的状态库 journal_mode: wal # 切到 WAL(预写日志,允许并发读写),并发必备 busy_timeout: 5000 # 锁等待毫秒数,防并发写时 "database is locked" monitoring: export: otlp: endpoint: http://collector:4318/v1/traces # OTLP 上报地址,验证指标全靠它 headers_env: Authorization: HERMES_OTLP_TOKEN # 取值放环境变量,配置里不写明文⚠️ 别一上来把max_connections拉到 200:提供商侧的并发配额通常先扛不住,池子调大只会让 429 来得更快。
📊 不同规模下参数怎么选
数据库连接数计算没有标准公式,核心只有一件事:你的并发出站请求有多少,每条连接平均占多久。三档推荐值如下:
| 参数 | 单机开发 | 中小生产 | 高并发集群 |
|---|---|---|---|
| max_keepalive_connections | 5 | 20 | 50 |
| max_connections | 20 | 100 | 300 |
| keepalive_expiry(秒) | 20 | 20 | 20 |
| timeout.pool(秒) | 5 | 10 | 10 |
| SQLite busy_timeout(毫秒) | 2000 | 5000 | 5000 + 只读副本 |
- 单机开发:就你一个用户加几个会话,并发上限天然低,给 20 够用;不够用时问题立刻暴露,调优反而没意义。
- 中小生产:100 是代码默认值,来源是"LLM 主调用并发 + 压缩、标题生成等后台任务"的叠加,keep-alive 20 约为它的五分之一,贴合常规 QPS。
- 高并发集群:按节点数线性放大前先查提供商限流(并发数 / RPM);Dockerfile 里甚至写死了 SQLite 版本校验,因为集群规模下先崩的往往是 SQLite 而不是出站池。
- keepalive_expiry 三档都锁死 20:这个值由反向代理的空闲超时(30-60 秒)约束,和你的规模无关。调大了,连接还没复用到就先被服务端掐断。
✅ 改完配置,怎么确认真的生效
改配置 ≠ 生效。出站参数目前硬编码在源码里,改完必须重启进程;OTLP 上报结构见 agent/monitoring/otlp_exporter.py(OTLP 即 OpenTelemetry 上报协议,span 指标经它进你的 Collector 可查),endpoint不配,下面一半指标看不见。
# 1) 确认源码常量已改且进程重启(预期输出新值 100) grep -n "max_connections" agent/process_bootstrap.py # 2) WAL 是否真的生效(预期输出 wal) sqlite3 state.db 'PRAGMA journal_mode;' # 3) 两类错误计数,调优后都应降到 0 grep -c "PoolTimeout" logs/gateway.log grep -c "database is locked" logs/gateway.log| 指标 | 验证什么 | 在哪看 |
|---|---|---|
| PoolTimeout 次数 | 池耗尽是否解决 | 上面命令 3,或日志面板 |
| 出站请求 P99 | 延迟是否真降 | Grafana 查httpxspan 的 duration p99,对比改动前后 |
| WAL 模式 | state.db 配置生效 | 命令 2 输出wal |
| database is locked 次数 | 并发写压力 | 命令 3 |
| gateway_health span 速率 | 监控管道活着 | Collector 面板查该 span 每秒计数,应稳定 |
前两项下降、后两项归零,闭环完成。若 WAL 输出不是wal,先跑python -c "import sqlite3; print(sqlite3.sqlite_version)"——低于 3.51.3 的版本有 WAL-reset 上游 bug。
⚠️ 翻车场景速查
连接池超时设置和 SQLite 的坑大多重复出现,按现象对号入座:
| 现象 | 根因 | 修复动作 |
|---|---|---|
高峰后 10 秒起间歇PoolTimeout | max_connections 偏小,或流式连接长时间不释放 | 先查提供商并发配额,再上调到 150;仍耗尽则排查长占用连接 |
空闲后首请求报peer closed connection | keepalive_expiry 大于代理空闲超时,复用了已被服务端关闭的连接 | keepalive_expiry 降到 20 秒,贴着代理 30 秒上限 |
并发写报database is locked | 还是默认回滚日志模式,读写互相阻塞 | 设journal_mode: wal+busy_timeout: 5000 |
| 全部请求挂起但无报错 | timeout.pool 太大,把池耗尽掩盖成慢 | 降到 5-10 秒快速失败,再查谁在占连接 |
| 部署后 state.db 异常、CPU 偏高 | 旧版 SQLite 的 WAL-reset bug | 升级 SQLite ≥ 3.51.3,Dockerfile 已内置该校验 |
代码里对这些 sqlite 异常的处理路径可看 agent/insights.py,排查日志时优先搜OperationalError关键字。
一句话收束:出站并发设上限、在代理超时前回收空闲连接、池满时快速失败。默认值看 agent/process_bootstrap.py,效果盯上面 4 个指标。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考