news 2026/9/27 21:17:59

undici PoolStats 指南:深入理解连接池与请求计数器快照

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
undici PoolStats 指南:深入理解连接池与请求计数器快照
  • 后端
  • 网络
  • 通信

【免费下载链接】undici

An HTTP/1.1 client, written from scratch for Node.js

项目地址:https://gitcode.com/gh_mirrors/un/undici
点击查看免费下载

PoolStats是 undici 提供的只读快照对象,用于在任意时刻查看Pool或BalancedPool的聚合连接数与请求计数器。本文以 PoolStats.md 文档为主体,结合仓库源码讲解其快照语义、六个核心属性的真实含义、底层聚合实现,以及用 TypeScript 类型与测试用例验证各属性的实际行为,帮助你掌握连接池的运行时观测与调优能力。

概述:PoolStats是什么

PoolStats暴露了某个Pool或BalancedPool的聚合连接与请求计数器的一个只读快照。它自 v4.15.0 引入,类型为 module,状态标记为Stability: 2 - Stable(稳定)。

文档明确强调了两点关键语义:

  1. 惰性创建:实例在每次访问pool.statsgetter 时被惰性创建,因此其中保存的值反映的是访问那一刻连接池的状态;
  2. 快照特性:实例是一次性快照,不会随连接池后续变化而更新;想要观察后续变化,需要再次读取pool.stats。

在正常使用中,你不需要直接构造PoolStats,而是通过 pool 的stats属性获取实例:

import { Pool } from 'undici' const pool = new Pool('http://localhost:3000') const { stats } = pool console.log(stats.connected, stats.running, stats.size)

类:PoolStats

每个属性都是一个普通的 number,在实例创建时根据连接池内部状态计算得出。由于实例是快照,若需持续观测,应周期性重新读取pool.stats。

new PoolStats(pool)

  • pool{Pool|BalancedPool}:要读取计数器的池对象。

该构造函数从pool的当前状态创建新的PoolStats实例。不过文档建议优先使用pool.statsgetter——它在每次访问时都会返回一个全新的PoolStats。

在源码 lib/util/stats.js 中可以看到PoolStats的实际实现,它读取的是 pool 内部通过符号键(symbol key)暴露的状态:

class PoolStats { constructor (pool) { this.connected = pool[kConnected] this.free = pool[kFree] this.pending = pool[kPending] this.queued = pool[kQueued] this.running = pool[kRunning] this.size = pool[kSize] } }

对应地,PoolBase在 lib/dispatcher/pool-base.js 中通过 getter 返回新实例:

get stats () { return new PoolStats(this) }

poolStats.connected

  • 类型:{number}

该池中打开的 socket 连接数。注意在 lib/dispatcher/pool-base.js 中,它是遍历池内所有 client 的kConnected求和得到的聚合值:

get [kConnected] () { let ret = 0 for (const { [kConnected]: connected } of this[kClients]) { ret += connected } return ret }

而单个 client 的kConnected定义在 lib/dispatcher/client.js:只有当 HTTP 上下文存在、未处于连接中且未被销毁时才为真值。

poolStats.free

  • 类型:{number}

该池中打开但当前没有活动请求的 socket 连接数。源码实现(lib/dispatcher/pool-base.js)中,free统计的是「已连接且不需要排空(not needDrain)」的连接数:

get [kFree] () { let ret = 0 for (const { [kConnected]: connected, [kNeedDrain]: needDrain } of this[kClients]) { ret += connected && !needDrain } return ret }

poolStats.pending

  • 类型:{number}

该池中所有 client 的待处理请求数。这里的pending是「已进入池内队列但尚未被实际执行」的请求。从 lib/dispatcher/pool-base.js 的实现可以看出,它等于池内队列长度kQueued加上所有 client 的kPending之和:

get [kPending] () { let ret = this[kQueued] for (const { [kPending]: pending } of this[kClients]) { ret += pending } return ret }

poolStats.queued

  • 类型:{number}

该池中所有 client 的排队请求数。它对应 PoolBase 内部的kQueued计数器(lib/dispatcher/pool-base.js):当所有 dispatcher 都处于忙状态(kNeedDrain)时,新请求会被压入FixedQueue并递增kQueued,等待某个 client 排空后从队列中取出(见 kDispatch 实现 与 kOnDrain)。

poolStats.running

  • 类型:{number}

该池中所有 client 的当前活动请求数。同样是遍历聚合的结果(lib/dispatcher/pool-base.js),每个 client 的kRunning为其「已派发但尚未完成的请求」数量。

poolStats.size

  • 类型:{number}

该池中所有 client 的活动、待处理或排队请求总数。从 lib/dispatcher/pool-base.js 看,它等于kQueued加上所有 client 的kSize之和,是反映池中「在途请求总量」的综合指标:

get [kSize] () { let ret = this[kQueued] for (const { [kSize]: size } of this[kClients]) { ret += size } return ret }

与 ClientStats 的区别:free 与 queued 是池级指标

undici 的Client也有自己的statsgetter,但返回的是ClientStats而非PoolStats。两者在 lib/util/stats.js 中并列实现:

class ClientStats { constructor (client) { this.connected = client[kConnected] this.pending = client[kPending] this.running = client[kRunning] this.size = client[kSize] } }

对比可见:

  • ClientStats只有connected、pending、running、size四个属性;
  • free(空闲连接)与queued(池级排队请求)是Pool/BalancedPool 专属指标,因为队列和空闲判定是连接池层级的调度行为,单个 client 无法独立表达。

通过Agent获取聚合统计

v7.9.0 起(对应 PR 4157),Pool 和 Client 的统计信息通过Agent暴露。在 lib/dispatcher/agent.js 中,Agent的statsgetter 会遍历其管理的所有 dispatcher,并按 origin 组织成一个普通对象:

get stats () { const allClientStats = {} for (const dispatcher of this[kClients].values()) { if (dispatcher.stats) { allClientStats[dispatcher[kUrl].origin] = dispatcher.stats } } return allClientStats }

因此使用Agent时,agent.stats的形态是Record<string, ClientStats | PoolStats>(见 types/agent.d.ts),每个 key 是 dispatcher 的 origin,value 是PoolStats或ClientStats实例,便于按域名维度观测流量分布。

TypeScript 类型定义

undici 为PoolStats提供了完整的类型声明(types/pool-stats.d.ts),六个属性均为number:

declare class PoolStats { constructor (pool: Pool) connected: number free: number pending: number queued: number running: number size: number }

Pool、RoundRobinPool与Agent的.d.ts中分别以TPoolStats引用了该类型(见 types/pool.d.ts、types/round-robin-pool.d.ts),确保 TypeScript 用户在访问pool.stats时获得类型安全提示。

测试用例验证:各属性在真实请求中的行为

仓库测试用例(test/pool.js)用真实请求验证了各计数器的取值逻辑。当一个请求正被处理(连接已建立、请求在途)时,断言如下:

t.strictEqual(client.stats.connected, 1) t.strictEqual(client.stats.free, 0) t.strictEqual(client.stats.pending, 0) t.strictEqual(client.stats.queued, 0) t.strictEqual(client.stats.running, 1) t.strictEqual(client.stats.size, 1)

对应关系一目了然:

  • 连接建立 →connected = 1;
  • 请求在途 →running = 1,free = 0(没有空闲连接);
  • 请求既不在池队列也不在 client 队列 →pending = 0、queued = 0;
  • 在途请求总数 →size = 1。

另有并发场景的断言(test/pool.js)展示了排队时的状态:当请求数超过连接数时,queued = max(n - connections, 0),pending = n,size = n;而 test/pool.js 中pool.stats.queued === 1验证了「第二个连接正在建立时,请求停留在池队列中」的行为。test/round-robin-pool.js 则用pool.stats.connected <= 2验证了轮询池连接数的上限。

实战:用 PoolStats 观测连接池健康度

综合文档与源码,PoolStats最典型的应用场景是周期采样连接池状态,从而判断是否需要扩容连接、限流或排查连接泄漏:

import { Pool } from 'undici' const pool = new Pool('http://localhost:3000', { connections: 10 }) setInterval(() => { const s = pool.stats console.log({ connected: s.connected, // 已打开 socket 数 free: s.free, // 空闲 socket 数,接近 0 说明连接吃紧 pending: s.pending, // 待处理请求数 queued: s.queued, // 池队列中排队的请求数 running: s.running, // 活动请求数 size: s.size // 在途请求总量 }) }, 1000)

解读要点:

  • size === connections且queued持续增长:连接池已满,请求开始排队,可考虑提高connections或增加实例;
  • free长期为 0 而running居高不下:连接利用率接近饱和;
  • connected明显低于预期但pending很大:可能处于连接建立阶段或存在连接建立瓶颈;
  • 所有计数器在空闲时应回归 0(或仅有 keep-alive 连接使connected > 0、free === connected),否则可能存在请求未释放的问题。

由于pool.stats每次访问都会创建新快照,务必在同一时刻一次性读取各属性(如上面的示例),避免在多次访问之间状态变化导致读数不一致。

  • 后端
  • 网络
  • 通信

【免费下载链接】undici

An HTTP/1.1 client, written from scratch for Node.js

项目地址:https://gitcode.com/gh_mirrors/un/undici
点击查看免费下载

相关推荐

上一篇:PortMaster进阶技巧:手动安装游戏移植包与版本更新完全指南
下一篇:通道剪枝优化Demucs:从原理到实现的完整实验指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 21:17:57

2026最新高端品质网站建设SEO避坑指南

2026最新高端品质网站建设SEO避坑指南 备案流程一头雾水?别急,先看看你的网站结构。很多设计师转前端,或者刚接触高端品质网站建设的朋友,一上来就盯着像素对齐、动效丝滑,结果上线三个月,百度收录为零,Google索引寥寥无几。2026年最新的搜索算法逻辑已经变了,光有“高大上”的视觉呈现,没有扎实…

作者头像 李华
网站建设 2026/9/27 21:17:33

建设营销网站时以什么为导向?安全速查手册

建设营销网站时以什么为导向?安全速查手册 自己不会代码想做网站,最头疼的不是界面好不好看,而是上线后数据丢没丢、后台被没被黑。很多老板觉得营销网站就是放个画册、接个表单,把预算全砸在UI和SEO上,结果上线两周,服务器被挂满挖矿脚本,或者用户信息被拖库。这时候你才反应过来,…

作者头像 李华
网站建设 2026/9/27 21:17:19

有做soho网站的吗?这份避坑指南专治备案一头雾水

有做soho网站的吗?这份避坑指南专治备案一头雾水 你是不是也在问“有做soho网站的吗”,结果一查发现备案流程一头雾水,根本不知道从哪下手?别急,这篇避坑指南就是专门给你准备的,不绕弯子,直接讲怎么把SOHO网站从想法变成线上生意。很多外贸SOHO、自由职业者卡在第一步,不是不会写代码,而是搞不懂…

作者头像 李华