一、子客户端是什么?
-
共享连接池:父/子实例共用 TCP 连接与健康状态,避免重复建连。
-
事件共享 :父与子共用同一个事件发射器(diagnostic 等)。
- 如果你对父 客户端做了扩展,子会继承;
- 若只对子 扩展,父不会受到影响。
-
可配项 :创建子客户端时,几乎可传入与
new Client()
相同的选项,但不能 覆盖连接池相关的选项(ssl/tls
、agent
、pingTimeout
、Connection
、resurrectStrategy
)。 -
关闭行为 :父或任一子 调用
close()
,所有共享该连接池的客户端都会被关闭。
二、快速上手
js
const { Client } = require('@elastic/elasticsearch')
const client = new Client({
cloud: { id: '<cloud-id>' },
auth: { apiKey: 'base64EncodedKey' }
})
const child = client.child({
headers: { 'x-foo': 'bar' } // 只对这个子实例生效的默认请求头
})
client.info().then(console.log, console.log)
child.info().then(console.log, console.log)
三、典型用法场景
1) 多租户隔离(按租户打标)
js
const tenantA = client.child({
headers: { 'x-tenant': 'A' },
opaqueIdPrefix: 'tenantA' // 便于链路追踪
})
const tenantB = client.child({
headers: { 'x-tenant': 'B' },
opaqueIdPrefix: 'tenantB'
})
2) 业务线分治(不同默认超时/压缩)
js
const searchClient = client.child({
requestTimeout: 10_000,
compression: 'gzip',
name: 'svc-search'
})
const ingestClient = client.child({
requestTimeout: 60_000, // 写入容忍更长
name: 'svc-ingest'
})
3) 可观测与诊断(标识 + 上下文)
js
const analytics = client.child({
name: 'analytics',
context: { service: 'analytics', region: 'us-west-2' }
})
// 共享的 diagnostic 事件仍然能区分来源(看 name/context/opaqueId)
client.diagnostic.on('response', (e) => {
// e.meta.name / e.meta.request.params.context
})
四、最佳实践清单
- 能用 child 就别 new :只是"默认请求选项不同"时,用
child()
既省内存又省握手成本。 - 别改连接池级别选项 :在子客户端里避免配置/覆盖
ssl/tls
、agent
、pingTimeout
、Connection
、resurrectStrategy
。 - 统一关闭 :应用退出时只需在父 或任一子 上
close()
一次即可。 - Cloud 场景勿 sniff:子客户端沿用父端策略;Cloud 背后是负载均衡,不需要嗅探。
- 链路可观测 :为每个子客户端设置
name
/opaqueIdPrefix
/context
,诊断日志更清晰。
五、反例与踩坑
- ❌ 把不同证书/代理放在子客户端里 :这会触及连接池级别配置,API 不允许。需完全独立的
new Client()
。 - ❌ 误以为 close 只关一个实例 :任意父/子
close()
会关闭所有共享该池的实例。 - ❌ 把大块业务逻辑写进请求 Hook 却未区分子实例 :请根据
name/headers/context
做分流,避免串线。
六、组合示例:多租户 + 搜索/写入分治
js
const base = new Client({ cloud: { id: '<cloud-id>' }, auth: { apiKey: '...' } })
const tenant = (code) => base.child({
headers: { 'x-tenant': code },
opaqueIdPrefix: `tenant-${code}`
})
const tAsearch = tenant('A').child({ name: 'A-search', requestTimeout: 10_000, compression: 'gzip' })
const tAingest = tenant('A').child({ name: 'A-ingest', requestTimeout: 60_000 })
// 使用
await tAsearch.search({ index: 'docs-A', query: { match: { q: 'hello' } } })
await tAingest.index({ index: 'docs-A', document: { id: 1, title: 'hi' } })
七、总结
- child client = 共享连接池的"轻量克隆",非常适合多租户、多业务线的默认配置隔离。
- 记住两条铁律:不改池级别配置 、任意一端 close 全家关。
- 配合
headers / requestTimeout / compression / name / opaqueIdPrefix / context
,就能在性能不打折的前提下实现清晰的隔离与可观测。