ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LangChain调用LLM超时?根因是DNS解析慢和iptables conntrack表溢出

LangChain调用LLM超时?根因是DNS解析慢和iptables conntrack表溢出 我先把这次事故的时间线摆出来某天下午线上基于 LangChain 的应用突然开始疯狂超时调用 LLM 接口的请求大面积失败监控告警直接刷屏。最初所有人都往模型服务和 API Key 上怀疑结果查了大半天根因竟是一连串看似毫不相关的底层问题叠加DNS 解析慢、iptables 的 conntrack 表溢出、UDP 53 端口丢包。整个过程就像剥洋葱每剥一层都以为到头了结果又冒出新坑。这篇文章就把这套完整的排查思路和修复过程记录下来希望能帮到那些正在被 LLM 调用超时折磨的人。先说清楚我这次遇到的环境一个生产环境的 Python 服务通过 LangChain 框架调用 OpenAI 兼容格式的远程 LLM API业务高峰期每秒并发请求不算高但 LLM 接口本身响应慢、耗时长链路相对敏感。出问题时应用日志里大量出现类似APIConnectionError、requests.exceptions.ReadTimeout之类的异常部分请求甚至直接挂起几十秒不返回。一开始我们以为是 LangChain 内部配置了太短的超时时间或者是服务端在限流所以完全没往操作系统网络层去想。后面每一步排查都印证了一个道理LLM 应用的稳定性不只是模型和框架的事DNS、防火墙、内核参数这些地基出问题照样能把上层拖垮。1. 事故现场LangChain 调用 LLM 突然“卡死”1.1 现象描述与业务影响当天 14:30 左右监控系统开始报警错误率从正常的 0.5% 一路飙升到 30% 以上而且持续时间超过 15 分钟没有自动恢复。业务方反馈用户端的回答响应明显变慢有人等了十几秒才收到结果有些人干脆直接看到“服务异常”的提示。这里的特征是“偶发但又高频”并不是所有请求都失败而是每隔几个请求就会出现一次失败请求耗时分布很不规律有的在连接阶段就报错有的则是响应读到一半突然中断。这个模式比“全部超时”要难查得多因为如果全部超时优先怀疑网络断了或者服务挂了但“部分成功部分失败”往往意味着链路中某个环节存在间歇性故障比如负载均衡抖动、连接池耗尽、DNS 缓存过期、防火墙丢包等。我先让运维拉取了最近半小时的负载均衡和后端服务的访问日志同时看了一遍 LangChain 侧的 trace。从日志看请求确实到达了我们的后端服务后端也正常发起了对 LLM 上游 API 的 HTTP 请求但其中一部分在等待响应阶段超时上游完全没有返回任何数据。这就基本排除了业务代码本身的逻辑问题焦点转移到了“我们的服务到 LLM 上游”这一段链路上。1.2 初期排查方向团队内部开了个短会大家第一反应是检查是不是 API Key 额度耗尽、账号欠费、上游服务故障或者是我们发出去的并发请求太多触发了限流。这些当然都有可能但我们不能靠猜得用数据说话。我当时做了三件事第一确认上游 LLM 服务商的状态页看是否有大规模故障公告结果没有第二检查 API Key 相关配置和配额发现额度充足第三写了一个最小化的 Python 脚本绕开 LangChain直接使用requests库去调用同一个 LLM 接口然后统计响应时间和错误率。脚本测试下来的结果非常关键直接调用同样会超时。这说明问题既不在 LangChain 框架也不在我们的业务代码里而是更底层的网络通信环节。不过这里要提醒一句绕开框架做最小化复现是排查问题的一个经典手段但也要注意别急着下结论。我当时也顺手检查了 LangChain 的版本和配置毕竟 LangChain 更新很快不同版本的默认超时行为不一样。后来确认本机装的 LangChain 版本对于那些OpenAI封装类确实有默认的max_retries和timeout设置但直连脚本都复现了所以框架的问题可以先放一放。2. 应用层排查日志和超时参数背锅2.1 LangChain 调用链路的超时控制很多人第一次接触 LangChain 时以为只需要设置一个openai_api_key就能默认把所有事情处理好。实际上LangChain 底层是通过openaiPython SDK 发起 HTTP 请求的而这个 SDK 的可配置项非常多其中最关键的就是超时时间和重试策略。以常见的ChatOpenAI为例你可以通过request_timeout参数控制 HTTP 请求超时时间也可以通过max_retries控制重试次数。如果你在初始化时没有显式设置这些参数SDK 会使用自己的默认值比如 OpenAI SDK 的默认超时可能是 600 秒重试次数可能是 2 次。这个默认值看起来很大好像不容易超时但反过来说一旦某个环节真的出了问题请求会在底层一直挂着不会快速失败给我们的感觉就是“调用卡住了”。我当时先检查了服务里所有ChatOpenAI初始化的代码确认超时时间和重试次数到底是什么配置。因为业务需要我们设置的超时是 30 秒按理说已经比 SDK 默认值激进很多不应该出现大量请求 30 秒都等不到一个响应。但问题恰恰在于30 秒都拿不到响应正说明请求在更早的环节就已经卡住了比如 DNS 解析或者 TCP 连接建立阶段。from langchain_openai import ChatOpenAI llm ChatOpenAI( modelgpt-4o-mini, openai_api_keysk-xxx, request_timeout30, max_retries2, )上面的代码设置了 30 秒超时意味着如果 30 秒内拿不到响应就会自动重试重试两次后仍然失败才向业务层抛出异常。我们在日志里看到的异常时间戳基本都集中在 90 秒到 120 秒区间说明至少经历了一次完整的超时加重试周期。2.2 什么情况下 LLM 调用会假死LLM 调用的超时并不只是“服务器不回数据”这一种情况。根据我的经验一次完整的 LangChain 调用至少包含下面几个阶段DNS 解析、TCP 握手、TLS 握手、HTTP 请求发送、服务端处理、响应接收。任何阶段的异常都可能表现为上层超时。而且有些阶段出问题的表现很隐蔽。比如 DNS 解析慢时你从应用层看就是一个socket.gaierror或者干脆是长时间的阻塞TCP 握手超时你会看到ConnectionErrorTLS 握手超时症状和连接超时很像若是上游处理逻辑慢则更多表现为ReadTimeout。这里我建议大家在设计 LLM 应用时把超时拆成“连接超时”和“读取超时”两个维度来看不要只设一个笼统的总超时。连接超时通常设置短一些比如 5 到 10 秒用于快速发现网络不可达读取超时则要根据模型实际响应时间设置得宽松一些比如 30 到 60 秒因为大模型生成文本确实需要比较长的时间。如果不区分这两类超时遇到网络问题时你的服务很容易长时间占用线程和连接池导致整体吞吐量骤降。3. 顺着系统日志摸到 DNS3.1 抓包与系统日志的证据应用层的排查走到这里基本可以确认问题出在网络链路。我让运维在跑业务服务的服务器上执行了tcpdump抓取发往 LLM API 服务端 IP 的网络包。抓包文件分析下来有一个惊人的发现在出现超时的时间段TCP 连接建立的三次握手是正常的TLS 握手也完成了但随后请求发出后对端迟迟没有返回数据。同时我在服务器上手动执行了一次 DNS 解析测试用dig和getent去解析 LLM API 的域名发现解析耗时波动非常大正常时是 10 毫秒左右异常时能达到 5 秒以上。这里就是突破口。LLM API 的域名解析走的是 DNS 系统如果解析慢整个请求都会卡在获取 IP 地址这一步表现就是“连接还没建立就已经超时了”。# 测试解析耗时 time getent hosts api.openai.com # 正常情况输出大约 0.01s异常情况可能达到 5s 以上 dig stats api.openai.com # 观察 Query time 这一项正常情况下应该在几十毫秒以内3.2 DNS 解析慢的原因分析DNS 解析慢可以从客户端、服务器端、中间网络三个层面找原因。我们当时先排查了服务器本机的resolv.conf发现里面配置了多个nameserver而且第一行指向了一个内网 DNS 服务器第二行才是指向公共 DNS 服务器。这里有个经典的坑当客户端要向第一个 DNS 服务器发起查询时如果这个服务器没有响应客户端并不会立刻切换到第二个而是要等待超时默认 5 秒再重试或者尝试下一个。如果第一个 DNS 服务器处于“半死不活”的状态比如它能收到请求但响应极慢那么每次解析都可能耗时 5 到 10 秒从而拖垮整个调用链路。再看/etc/nsswitch.conf中的hosts条目配置了files dns也就是先查/etc/hosts再查 DNS。当时我们也怀疑是不是/etc/hosts里有异常条目但检查后并没有发现问题。于是重点落在了 DNS 服务器本身的响应质量上。# 查看当前 DNS 配置 cat /etc/resolv.conf # 内容示例 nameserver 10.10.0.1 nameserver 8.8.8.8 options timeout:5 attempts:2从resolv.conf的内容可以看出系统默认的timeout是 5 秒attempts是 2 次。这意味着在最坏情况下如果第一个 DNS 服务器完全不可达解析一个域名可能需要 5 秒 × 2 次 10 秒而且这只是单个域名查询的耗时。LangChain 调用 LLM 前除了要解析 API 域名还可能加载一些元数据、身份验证端点等加起来总耗时非常可观。我当时做了一个简单的模拟测试临时修改resolv.conf把timeout缩短到 1 秒attempts改为 1 次再调用直连脚本超时率立刻下降了很多。这个操作本身只是缓解症状并不能根治上游 DNS 服务器响应慢的问题但它确实验证了 DNS 解析是超时的重要诱因。3.3 DNS 缓存与复用问题除了 DNS 服务器的响应速度本地 DNS 缓存策略也是一个大坑。Linux 系统默认的 DNS 缓存行为由 nscd、systemd-resolved 或 dnsmasq 等组件决定。如果服务没有做 DNS 缓存每次发起新的 LLM 请求都会重新解析域名在高并发场景下会产生大量重复的 DNS 查询既增加延迟又给 DNS 服务器带来压力。我们当时检查了一下服务器并没有启用 systemd-resolved也没有安装 dnsmasq所以理论上每次请求都会直接发起真实的 DNS 查询。这就是为什么偶发的 DNS 慢直接反映到了每一次 LLM 调用上。为了验证我对比了有缓存和无缓存两种情况下解析耗时的分布显然有缓存时稳定很多。后来我们在服务内部加了 DNS 缓存同时调整了底层 HTTP 客户端的连接复用策略保持长连接到 LLM API 服务器避免每次请求都重新解析和建立连接。这个动作对降低 P99 延迟非常有效。但紧接着新的问题又出现了这次和 iptables 扯上了关系。4. 坑中坑iptables 规则把 DNS 流量“搞挂了”4.1 iptables 与 conntrack 的原理误解DNS 慢的问题找到后我们以为调整完 DNS 就能收工。然而过了几个小时监控显示 LLM 调用的超时率虽然比之前低了很多但仍然比正常水平高出一个数量级而且出现了一些奇怪的规律每次超时都发生在业务流量高峰期。我再次抓包分析这次发现更微妙的现象服务器发出的 DNS 查询请求正常到达了 DNS 服务器但响应包没有回来。由于 DNS 用的是 UDP 协议无连接、无重传一旦中间某个环节丢了包客户端只会傻等超时。那到底是谁丢了 DNS 响应包排查到这一步注意力放到了防火墙。这台服务器上配置了 iptables 规则而且负载较高。我首先查看了 conntrack 连接跟踪表的当前状态和容量限制。conntrack模块会为每一个经过服务器的连接或数据包建立跟踪记录DNS 查询虽然用的是 UDP但同样会被跟踪。如果/proc/sys/net/netfilter/nf_conntrack_max设置得比较小而服务器同时有大量其他连接比如业务服务之间的调用、日志上报、监控采集等连接跟踪表会在高峰期被打满。当表满时新的连接或数据包会被丢弃表现就是 DNS 查询发出去没有响应。UDP 本身不可靠丢包后如果没有上层重试业务层就只能干等。# 查看当前连接跟踪表使用量 sysctl net.netfilter.nf_conntrack_max cat /proc/sys/net/netfilter/nf_conntrack_count # 查看 conntrack 超时配置 sysctl net.netfilter.nf_conntrack_udp_timeout sysctl net.netfilter.nf_conntrack_udp_timeout_stream我遇到的情况就是nf_conntrack_count经常达到nf_conntrack_max的 95% 以上高峰期甚至会打满。一旦打满不只是 DNS任何新建立的连接都可能受影响。连 SSH 都会变得卡顿。4.2 常见的 iptables 误配案例这里顺便聊聊我见过的几种 iptables 配置问题很多人对 iptables 的理解停留在“放行端口”和“禁止 IP”上很少考虑状态跟踪和性能影响。第一种是规则顺序错误。iptables 规则是按顺序匹配的如果一个范围较大的DROP规则写在了ACCEPT规则前面会把本来应该放行的流量也丢掉。比如有些人为了“防扫描”在INPUT链前面加了一条针对所有 UDP 端口的限制规则但忘了 DNS 响应包就是经过INPUT链进入本机的。第二种是使用-p udp --dport 53 -j DROP这类规则本意是想屏蔽外部对本地 53 端口的访问结果把 DNS 服务的入站查询全部拦掉了。如果本机就是一台 DNS 转发服务器这就是自杀式配置。第三种是大量使用-m state或-m conntrack时没有结合系统参数调优。比如有人为了让防火墙更“安全”给每条规则都加上状态匹配这虽然不会导致功能错误但在超高并发下会加重 conntrack 模块的负担间接引发丢包。我这次遇到的并不是某条规则写错了而是 conntrack 表容量不足导致的丢包属于明显的资源耗尽型故障。平时系统空闲时没有问题一到流量高峰就暴露无遗。这也是它不容易被第一时间发现的原因光看 iptables 规则列表看不出任何毛病因为它根本不在规则本身。4.3 为什么是连环坑DNS 与 iptables 叠加很多人会问DNS 解析慢和 iptables conntrack 表满看起来是两件毫不相关的事为什么会在一次事故里同时出现关键在于调用链路的放大效应。LangChain 每次调用 LLM可能涉及多个域名解析和多次 HTTP 请求。如果 DNS 解析本来就慢那么单个请求耗时会变长请求变长意味着服务端同时持有的并发连接数更多连接数变多conntrack 表的压力随之上升表满后开始丢包丢包导致 DNS 解析更慢同时也会造成 TCP 连接建立超时。于是形成一个恶性循环DNS 慢导致 conntrack 表压力大进而导致 DNS 更慢最终表现在应用层就是大规模 LLM 调用超时。理解了这个循环就能解释为什么在高峰期问题特别严重而在低峰期测试一切正常。单纯调优 DNS 参数只能减轻问题不能根治。我们必须同时破解两个瓶颈。5. 尘埃落定完整修复与长期防护5.1 修复 DNS 配置第一步修改/etc/resolv.conf把超时时间缩短同时增加快速切换 DNS 服务器的选项。以我最终使用的配置为例nameserver 10.10.0.1 nameserver 8.8.8.8 options timeout:1 attempts:2 single-request-reopen参数说明timeout:1表示每次 DNS 查询的等待时间从默认的 5 秒缩短到 1 秒。attempts:2表示重试次数为 2 次最坏情况下的等待时间从 10 秒缩短到 2 秒。single-request-reopen是很多 Linux 发行版必须加的选项主要解决 IPv4 和 IPv6 DNS 查询并发冲突导致的解析延迟问题。如果发现服务器使用getaddrinfo查询时经常卡住加上这个参数通常会改善很多。不过要注意直接修改/etc/resolv.conf的做法可能被 systemd-resolved 或 NetworkManager 覆盖。建议根据系统版本统一配置。比如使用 systemd 的系统可以通过配置/etc/systemd/resolved.conf来设置 DNS 服务器和缓存策略确保配置持久化。另外我强烈建议在服务器上启用一个本地 DNS 缓存服务比如systemd-resolved、dnsmasq或unbound。本地缓存可以把频繁调用的 LLM API 域名解析耗时降到接近 0 毫秒极大降低对外部 DNS 服务器的依赖。5.2 修复 iptables 规则与 conntrack 表第二步优化 conntrack 相关内核参数。根据服务器的实际内存和业务规模适当调大nf_conntrack_max同时缩短 UDP 连接跟踪的超时时间让无用的连接记录尽快释放。# 调整 conntrack 最大连接数 sysctl -w net.netfilter.nf_conntrack_max1048576 # 缩短 UDP 连接跟踪超时 sysctl -w net.netfilter.nf_conntrack_udp_timeout30 sysctl -w net.netfilter.nf_conntrack_udp_timeout_stream60 # 持久化配置 echo net.netfilter.nf_conntrack_max1048576 /etc/sysctl.conf echo net.netfilter.nf_conntrack_udp_timeout30 /etc/sysctl.conf echo net.netfilter.nf_conntrack_udp_timeout_stream60 /etc/sysctl.conf sysctl -pnf_conntrack_max设置多大合适一般来说每个连接大概占用几百字节的内存64 位系统上每个 conntrack 条目大约占用 300 到 400 字节。如果服务器内存是 16GB设置成 1048576 是安全的如果内存只有 4GB建议设置为 262144 左右避免 conntrack 表本身抢占过多内存。此外要审视现有 iptables 规则删除不必要的 DROP 和 REJECT 规则尤其是那些针对 UDP 53 端口的高风险规则。在服务器上执行以下命令输出所有规则再逐一确认iptables -L -n -v --line-numbers iptables -t nat -L -n -v --line-numbers iptables -t mangle -L -n -v --line-numbers排查规则时更推荐把配置导出成文件再检查比如iptables-save /tmp/iptables.rules然后在文件里搜索53、DROP、REJECT等关键词逐条分析。有些规则是以前为了临时解决问题加进去的后来忘了删这种历史遗留问题手写检查最靠谱。5.3 LangChain 应用侧加固底层问题修复后上层应用也要做相应的加固否则即使底层网络偶发抖动业务依然可能超时失败。这里我分享一下 LangChain 调用 LLM 时的几个实用配置。首先是设置合理的重试策略。在ChatOpenAI的初始化中max_retries可以配置重试次数但要注意重试也会占用线程时间。如果底层网络已经持续故障重试太多次只会加剧资源消耗。我最终把max_retries设为 3配合指数退避算法让重试间隔逐渐增大。其次是连接池和请求会话的复用。openaiSDK 本身支持自定义http_client我们可以传入一个配置了连接池大小的httpx.Client避免高频请求时反复创建连接。示例代码如下import httpx from langchain_openai import ChatOpenAI http_client httpx.Client( timeouthttpx.Timeout(30.0, connect5.0), limitshttpx.Limits(max_connections50, max_keepalive_connections20), ) llm ChatOpenAI( modelgpt-4o-mini, openai_api_keysk-xxx, request_timeout30, max_retries3, http_clienthttp_client, )这里把连接超时和读取超时分开设置连接阶段 5 秒没有建立成功就快速失败读取阶段则给足 30 秒。HTTP 连接池复用长连接也减少了对 DNS 解析的依赖。最后建议在 LangChain 外层增加一个基于信号的超时控制或者使用asyncio的方式保证最坏情况下请求一定能被终止并返回错误而不是无限等待。5.4 监控与巡检这次事故最大的教训是很多底层指标平时不看出问题时才去查非常被动。事后我们把 DNS、conntrack 等指标全部接入了监控系统设置阈值告警防患于未然。我建议至少监控以下指标DNS 解析耗时包含getent或dig的耗时统计。/proc/sys/net/netfilter/nf_conntrack_count的实时值与nf_conntrack_max相比超过 70% 时就预警。系统 TCP 连接状态分布重点关注SYN_SENT、TIME_WAIT、ESTABLISHED的数值波动。LLM API 调用的成功率、P99 延迟、具体错误类型。6. 常见问题与避坑清单6.1 故障排查速查表为了方便大家在类似问题出现时快速定位我整理了一个速查表覆盖我这次踩过的坑和一些经典的排查手段。现象排查命令常见根因解决方向LangChain 调用 LLM 偶发超时先看应用日志和 trace再用直连脚本复现应用配置、网络链路、上游服务分层排查定位卡在哪个阶段DNS 解析耗时高time getent hosts 域名、dig stats 域名上游 DNS 慢、本地缓存缺失、resolv.conf 配置不当调整 timeout/attempts启用本地 DNS 缓存解析耗时不规则忽高忽低连续执行多个域名解析观察波动多 nameserver 切换慢、IPv4/IPv6 查询冲突加single-request-reopen精简 nameserver高峰期丢包平时正常查看 conntrack 表使用量nf_conntrack_max 过小表满丢包调大 conntrack 上限缩短 UDP 超时新增规则后出现超时iptables -L -n -v逐条核对规则顺序错误、误 DROP DNS 流量修正规则顺序删除多余 DROP连接超时和读取超时互相混淆使用httpx.Timeout区分阶段配置了单一全局超时定位困难拆分配置 connect/read 超时请求卡住不返回抓包确认 TCP/TLS/DNS 哪个阶段卡住底层链路某个环节丢包tcpdump 分段分析6.2 三条独家经验经验一遇到 LLM 调用超时先别急着怪模型。我在这次故障中吃了不少亏因为 LangChain 和 LLM 这种上层组件很容易让人先入为主地认为问题出在模型或 API 服务端。实际上很多超时在网络链路尤其要排查 DNS。先把直连脚本写好在不经过框架的情况下打一下上游接口就能快速把问题范围缩小。经验二改 DNS 配置时single-request-reopen一定要加上。很多系统默认不带这个选项在开启了 IPv6 的环境下getaddrinfo解析域名时会在 A 和 AAAA 记录查询之间产生竞态导致整个解析过程被拖慢到几秒甚至更长。加上这个配置后即使不使用 IPv6也能避免一个非常隐蔽的延迟源。经验三conntrack 表要监控不要只在 iptables 规则层面打转。iptables 只是防火墙规则的外在表现真正影响性能的是内核里的连接跟踪模块。很多人查 iptables 规则查了半天都查不出问题却不知道nf_conntrack_count已经超过了上限。把 conntrack 的使用率加入监控设置合理告警阈值比花大量时间研究规则顺序高效得多。这次事故处理完我再回看那天的过程最大的感受是故障本身不可怕可怕的是排查思路被上层框架带着走。只要牢牢把握链路分层的原则从应用一层层往下剥DNS 到 iptables 这些底层问题迟早会暴露出来。最后再分享一个实用小习惯每次排查完类似问题顺手把当时的关键日志、命令输出和配置变更记录保存下来下一次再遇到相似场景能省下至少一半的时间。
返回列表