Elasticsearch JS 客户端子客户端(Child Client)实践指南
一、子客户端是什么?
-
共享连接池:父/子实例共用 TCP 连接与健康状态,避免重复建连。
-
事件共享:父与子共用同一个事件发射器(diagnostic 等)。
- 如果你对父客户端做了扩展,子会继承;
- 若只对子扩展,父不会受到影响。
-
可配项:创建子客户端时,几乎可传入与
new Client()
相同的选项,但不能覆盖连接池相关的选项(ssl/tls
、agent
、pingTimeout
、Connection
、resurrectStrategy
)。 -
关闭行为:父或任一子调用
close()
,所有共享该连接池的客户端都会被关闭。
二、快速上手
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) 多租户隔离(按租户打标)
const tenantA = client.child({headers: { 'x-tenant': 'A' },opaqueIdPrefix: 'tenantA' // 便于链路追踪
})const tenantB = client.child({headers: { 'x-tenant': 'B' },opaqueIdPrefix: 'tenantB'
})
2) 业务线分治(不同默认超时/压缩)
const searchClient = client.child({requestTimeout: 10_000,compression: 'gzip',name: 'svc-search'
})const ingestClient = client.child({requestTimeout: 60_000, // 写入容忍更长name: 'svc-ingest'
})
3) 可观测与诊断(标识 + 上下文)
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
做分流,避免串线。
六、组合示例:多租户 + 搜索/写入分治
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
,就能在性能不打折的前提下实现清晰的隔离与可观测。