在MongoDB的聚合管道里,系统变量以美元符号加关键字的形式存在,其中$host用于返回执行该管道阶段的服务器主机信息。它通常在副本集或分片集群环境下才有意义,因为单机实例永远只返回同一个本地地址。理解$host的构成,可以让我们把数据按物理节点做切分统计,而不必把海量文档拉到应用层再去分辨来源。

一、$host变量的底层结构与取值逻辑
MongoDB在聚合管道中预留了若干系统变量,例如$$ROOT、$$CURRENT,而$host属于只读的服务器上下文变量。当聚合引擎在某一台mongod或mongos上执行管道阶段时,$host会被替换为该进程监听的hostname:port字符串。在副本集中,主节点与从节点各自返回自己的地址;在分片集群里,mongos会把子查询下发到不同分片,各分片返回自身$host,最终由mongos汇总。
需要注意的是,$host并不是文档里的字段,而是引擎注入的变量,因此不能在find查询里直接使用,只能出现在聚合表达式内部。它的取值不依赖集合数据,所以即便集合为空,只要管道跑起来就能拿到值。下面这段管道演示了如何把每条文档打上来源主机标签:
db.getCollection('orders').aggregate([
{
$project: {
_id: 0,
orderId: 1,
fromHost: "$host"
}
}
]);
上述代码在每台节点上执行时,fromHost都会变成类似shard01:27018这样的字符串。如果业务需要区分不同机房,可以在应用层对$host做正则截取,或者借助$expr与$regexFind在管道内直接解析。
二、结合$group实现按主机名的负载统计
单纯取出$host往往不够,更常见的需求是看每台机器处理了多少文档、平均响应字段大小等。这时可以把$host放进$group的_id中,配合累加器完成服务端聚合。相比把数据传回客户端再用代码分组,这种做法大幅减少了网络IO,也避免了应用服务器内存膨胀。
举例来说,假设有一个记录用户操作的events集合,我们希望知道过去一天各分片写入量差异。可以使用如下聚合:
db.getCollection('events').aggregate([
{
$match: {
ts: { $gte: new ISODate("2023-01-01T00:00:00Z") }
}
},
{
$group: {
_id: "$host",
count: { $sum: 1 },
avgPayload: { $avg: { $strLenCP: "$payload" } }
}
},
{
$sort: { count: -1 }
}
]);
该管道先在时间范围上过滤,然后按主机名分组计数并算平均负载长度,最后按数量倒序。执行结束后,运维人员能直观看到是否某一分片热点明显。如果$host返回的是mongo-shard-a:27017,那说明分片A承载了更多写入。此方案比依赖外部监控Agent更轻量,特别适合临时排查。
另外,在$group里还可以嵌套$push收集样本ID,但要小心内存上限。MongoDB默认聚合内存限制为100MB,若某主机文档极多,应开启allowDiskUse: true把中间结果落盘,否则会抛出超出内存的错误。
三、$host与客户端host属性的差异及使用边界
很多开发者容易混淆服务端$host与驱动连接配置中的host参数。驱动层面的host指的是客户端想要连的入口地址,比如mongos的VIP;而聚合里的$host是实际干活的节点地址。在分片集群中,这两者可能完全不同:客户端连的是router.ipipp.com:27017,但文档实际由shard02:27017返回,聚合结果中的$host反映的是后者。
从权限角度看,$host不需要额外授权,任何能运行聚合的角色都能读取。但它暴露了内网拓扑,因此在对外开放的只读接口上要谨慎使用,避免泄露端口与机器命名规律。如果必须对外提供统计,建议在管道里用$project把$host映射成抽象节点编号:
db.getCollection('logs').aggregate([
{
$project: {
node: {
$switch: {
branches: [
{ case: { $eq: ["$host", "shard01:27017"] }, then: "NODE_1" },
{ case: { $eq: ["$host", "shard02:27017"] }, then: "NODE_2" }
],
default: "OTHER"
}
},
msg: 1
}
}
]);
这段逻辑把真实主机名翻译成业务侧易懂的标签,既保留了按节点分析的能力,又隐藏了基础设施细节。总体来看,$host是MongoDB聚合中一个小但实用的变量,掌握它可以帮助团队用纯数据库手段完成节点级观测,减少对外部脚本的依赖。