<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>线程池 on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/</link>
        <description>Recent content in 线程池 on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Sun, 11 Oct 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>asyncio 接口集体超时：同步调用阻塞事件循环</title>
            <link>https://blog.5772447.xyz/posts/5255de1b/</link>
            <pubDate>Sun, 11 Oct 2026 09:00:00 +0800</pubDate>
            <guid>https://blog.5772447.xyz/posts/5255de1b/</guid>
            <description>&lt;h2 id=&#34;问题背景&#34;&gt;&lt;a href=&#34;#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;工单聚合服务是客服系统的入口层：聚合工单、客户、物流三块数据后返回给坐席工作台。服务用 Python 3.11 + FastAPI + uvicorn 部署在 K8s 上，4 个 worker，平时 300 QPS 上下，p99 稳定在 300ms 以内。两周前上线了一个小功能——坐席点开工单详情时顺带查询关联包裹的物流轨迹，数据源是物流厂商提供的同步 SDK。&lt;/p&gt;&#xA;&lt;p&gt;这个功能代码量不到一百行，测试环境点了十几单都正常，顺利上线。它的第一周风平浪静，直到月末结算日：当天上午 10:20 开始，客服大面积反馈&amp;quot;工作台转圈&amp;quot;，监控上 p99 一路飙到 8 秒以上，部分请求直接超时断开。&lt;/p&gt;&#xA;&lt;h2 id=&#34;故障现象&#34;&gt;&lt;a href=&#34;#%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;故障现象&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;延迟集体恶化且不均匀：同一分钟内，大部分请求 200ms 内返回，但有一批请求卡 5~10 秒后失败——不是整体匀速变慢，而是&amp;quot;一批一批地冻住&amp;quot;。&lt;/li&gt;&#xA;&lt;li&gt;资源全是闲的：故障期间 CPU 12%、内存平稳、GC 正常、数据库连接池正常、下游依赖的延迟也没变化——常规性能瓶颈一个都对不上。&lt;/li&gt;&#xA;&lt;li&gt;日志出现时间断层：某些日志相邻两行的时间戳差了好几秒，而这两行之间只是普通的参数校验，理论上应该是微秒级。&lt;/li&gt;&#xA;&lt;li&gt;存活探针开始间歇失败，3 个 Pod 先后重启，处理中的请求被掐断，客户端重试又把流量抬高了一截。&lt;/li&gt;&#xA;&lt;li&gt;回滚掉两周前的物流查询功能后故障消失——此时已过去 40 分钟，高峰也基本过去了。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;排查过程&#34;&gt;&lt;a href=&#34;#%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;第一步，先证伪常见的四类瓶颈。&lt;/strong&gt; CPU、内存、GC、连接池、下游依赖全部正常，而 p99 暴涨——&amp;ldquo;资源全闲、延迟爆表&amp;quot;这个组合本身就指向一个方向：请求不是在等资源，而是在排队；让它们排队的不是 I/O，是执行机会。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第二步，抓住日志时间断层。&lt;/strong&gt; 参数校验这种纯同步代码出现秒级断层，说明进程在那一刻&amp;quot;没人干活&amp;rdquo;——不是慢，是停了。同步代码不会自己停几秒，唯一解释是它所在的线程根本得不到执行。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第三步，给事件循环装上&amp;quot;测速仪&amp;quot;。&lt;/strong&gt; 在服务里加了一个后台协程：每 100ms 醒来一次，用 &lt;code&gt;loop.time()&lt;/code&gt; 记录自己实际睡了多久。正常情况下漂移在几毫秒内；故障复现时，这个&amp;quot;事件循环延迟&amp;quot;直接冲到 3.8 秒。证据坐实：事件循环被长时间独占，同循环上所有协程集体饿死。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第四步，用 py-spy 定位是谁占着循环。&lt;/strong&gt; &lt;code&gt;py-spy dump&lt;/code&gt; 可以在生产 Pod 上免重启抓调用栈，主线程的栈停在 ssl 模块的 socket read 里，上层是物流 SDK 的 requests 调用，再上层正是新增的轨迹查询 handler——它写成了 &lt;code&gt;async def&lt;/code&gt;，函数体里却直接调了同步 SDK。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;第五步，解释&amp;quot;为什么是间歇性&amp;quot;。&lt;/strong&gt; 4 个 uvicorn worker 各自有一个事件循环，负载轮询分配。只有恰好落到&amp;quot;正在被同步调用阻塞&amp;quot;的那个 worker 上的请求才会冻结；而且第三方 SDK 的响应时间随对方负载波动（月末对账高峰恰好是他们的慢时段），阻塞时长时短，故障看起来就飘忽不定。这也解释了测试环境为何复现不了——压不出并发，也赶不上对方慢。&lt;/p&gt;&#xA;&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;解决方案&#xD;&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;止血&lt;/strong&gt;：把同步 SDK 调用包进 &lt;code&gt;asyncio.to_thread()&lt;/code&gt;，配一个专用线程池（&lt;code&gt;max_workers&lt;/code&gt; 显式设为 20，不用默认的无界池），并加 &lt;code&gt;asyncio.wait_for&lt;/code&gt; 5 秒超时；热修复滚动发布后 p99 回落到 400ms 以内。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;加固&lt;/strong&gt;：事件循环延迟接入告警（漂移超 500ms 预警、超 2s 升级）；延长优雅下线时间减少重启时的请求掐断；客户端重试改为指数退避，抑制重试风暴。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;治本&lt;/strong&gt;：推动物流厂商 SDK 的异步化（httpx 异步客户端版本），过渡期内所有同步依赖统一走线程池通道。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;防回归&lt;/strong&gt;：CI 引入 flake8-async / ruff 的 ASYNC 规则族，拦截 &lt;code&gt;async def&lt;/code&gt; 里直接调用同步 requests、&lt;code&gt;time.sleep&lt;/code&gt;、阻塞式文件打开等写法；代码评审清单新增一条&amp;quot;新增依赖必须声明同步还是异步&amp;quot;。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;根因分析&#34;&gt;&lt;a href=&#34;#%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;直接根因是&lt;strong&gt;在 asyncio 事件循环里直接调用同步阻塞的 SDK&lt;/strong&gt;：同步网络 I/O 不会让出控制权，事件循环被按住在那一刻，同一个 worker 上所有协程一起停摆。深层根因有两个：一是心智模型错位——&amp;ldquo;函数写成 &lt;code&gt;async def&lt;/code&gt;&amp;ldquo;被当成了&amp;quot;它不阻塞&amp;rdquo;，而 Python 的异步是协作式的，只要有一处不同步地阻塞，整个循环一起遭殃；二是监控盲区——团队盯了 CPU、内存、GC 所有资源指标，却没有&amp;quot;事件循环延迟&amp;quot;这个直指调度健康的指标，故障发生 20 分钟后才靠人肉发现日志断层。&lt;/p&gt;&#xA;&lt;h2 id=&#34;预防措施&#34;&gt;&lt;a href=&#34;#%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;事件循环延迟纳入所有 asyncio 服务的标准监控与告警；&lt;/li&gt;&#xA;&lt;li&gt;async handler 的外部调用四选一：异步客户端 / &lt;code&gt;asyncio.to_thread&lt;/code&gt; / 进程池 / 拆到独立服务，禁止裸调同步依赖；&lt;/li&gt;&#xA;&lt;li&gt;压测场景必须包含&amp;quot;慢依赖&amp;quot;注入（代理延迟或故障注入），不能只测快路径；&lt;/li&gt;&#xA;&lt;li&gt;探针与重启策略评审：评估重启对请求处理中任务与重试放大效应的影响；&lt;/li&gt;&#xA;&lt;li&gt;py-spy 纳入生产排障工具箱，定期演练免重启抓栈。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;总结&#34;&gt;&lt;a href=&#34;#%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;这次事故最值得记住的一幕，是&amp;quot;所有资源都闲、所有请求都在转圈&amp;rdquo;——它几乎是在明说：问题不在资源，在调度。asyncio 用得好，是把并发做得又轻又省；用不好，就是一根同步调用卡死整条流水线。给团队留下的三件东西：一个监控指标（事件循环延迟）、一条禁令（async 里不裸调同步）、一个工具（py-spy），比这次故障本身值钱得多。&lt;/p&gt;&#xA;&lt;h2 id=&#34;延伸阅读&#34;&gt;&lt;a href=&#34;#%e5%bb%b6%e4%bc%b8%e9%98%85%e8%af%bb&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;延伸阅读&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/d5be3b26/&#34; &gt;requests 连接池耗尽致间歇性 ConnectionError&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/21e8a43e/&#34; &gt;liveness 探针过严致 Pod CrashLoopBackOff&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/963ab042/&#34; &gt;一次服务健康检查设计 Checklist：探活、依赖、就绪、优雅下线三件套&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
        </item></channel>
</rss>
