Elasticsearch的分布式架构把索引切分成多个分片,文档写入时会根据一个路由值决定它归属哪个分片。这个路由值就是routing。大多数开发者在默认配置下从未显式接触过它,因为ES默认用文档的_id参与分片计算。但当你的查询场景高度集中在某个业务维度上时,合理使用routing字段可以大幅减少查询需要扫描的分片数量,是性能优化中性价比极高的手段。本文将从原理、用法、风险三个层面完整讲清楚routing。

一、routing的底层计算原理
理解routing首先要明白分片位置的确定逻辑。Elasticsearch内部使用一个类似MurmurHash3的哈希函数对路由值计算哈希,再对分片数量取模,公式可以简化表示为:
// 伪代码:计算文档所在分片 shard = hash(routing_value) % number_of_primary_shards
默认情况下,routing_value就是文档的_id。这意味着_id相同的文档必然落在同一个分片,但业务上相关的文档(例如同一个用户的多条订单)会被打散到不同分片。而一旦显式指定routing,计算结果就完全由你传入的值决定。举个典型例子:以user_id作为routing写入100万条订单,同一用户的所有订单会集中在单个分片上。
这里有一个非常关键的推论:routing参与分片计算,决定了文档的物理位置。因此写入时用了什么routing,查询、更新、删除时必须传同样的routing,否则ES根本找不到这篇文档。这是routing使用中最常见的翻车点,很多团队在写入时指定了routing,查询时忘了带,导致明明数据存在却查不到。
二、写入与查询时如何使用routing
使用routing的方式非常直接。写入文档时,把routing值放在URL查询参数或请求体中均可:
// 方式一:URL参数
PUT /orders/_doc/1?routing=user_10086
{
"user_id": "user_10086",
"amount": 199.00,
"status": "paid"
}
// 方式二:bulk批量写入,每条操作单独指定routing
POST /_bulk
{ "index": { "_index": "orders", "_id": "1", "routing": "user_10086" } }
{ "user_id": "user_10086", "amount": 199.00 }
{ "index": { "_index": "orders", "_id": "2", "routing": "user_10086" } }
{ "user_id": "user_10086", "amount": 58.50 }查询时同样携带routing参数,ES就只会把请求转发到对应的那一个分片,而不是广播到所有分片再汇总:
// 只会命中一个分片,而不是扫描全部分片
GET /orders/_search?routing=user_10086
{
"query": {
"term": { "user_id": "user_10086" }
}
}更新和删除也一样,只要操作指定了routing的文档,就必须带上routing,否则ES会返回404文档未找到的错误。此外,还可以在映射层面通过index.routing_path(新版本)或早期版本的自定义路由插件,把某个字段声明为routing来源,写入时自动取该字段值,减少调用方遗漏routing的概率。
性能收益体现在哪?假设索引有20个主分片,普通查询需要协调节点把请求发到20个分片、聚合20份结果;带上正确routing后只请求1个分片。在大分片数、高并发查询的场景下,网络开销、线程池占用和查询延迟都会有明显下降,官方在合理场景下报告过数量级的性能提升。
三、routing带来的数据倾斜风险与应对
routing不是免费的午餐,最大的代价是数据分布不均。默认按_id哈希时,文档近乎均匀地散布到各分片;改成按业务字段routing后,如果某些routing值对应的文档量特别大(例如一个大租户的数据占总量30%),就会造成个别分片体积和负载远超其他分片,出现热点问题。
应对策略有几种思路。第一,评估业务数据分布,只有当单个routing值的文档量可控(比如单用户数据量天然有限)时才适合启用routing。第二,对于数据量差异极大的场景,可以考虑routing加后缀打散,例如user_10086_0到user_10086_3四个路由值,查询时并发查四个routing再合并,牺牲一部分路由精度换取均衡。第三,结合索引别名和过滤,为不同量级的租户分配不同索引,从根本上避免倾斜。
还有一个容易被忽略的细节:主分片数量一旦确定就不可更改(除非reindex),而routing计算依赖分片数。如果你将来需要调整分片数,routing的分布结果会全部变化,历史数据的实际存储位置与按新分片数计算的位置不一致,必须通过reindex或_split/_shrink等操作配合处理,提前规划好容量非常重要。
四、routing与_id的关系及最佳实践
启用自定义routing后,文档的唯一性标识实际上变成了_id + routing的组合。也就是说,同一个_id配合不同的routing可以在同一索引中存在多篇文档。这在某些去重场景下是坑,也可能被有意利用来实现多维度存储。
如果希望强制routing与_id绑定,可以在写入时使用_id由routing推导的策略,或者在mapping中设置_source之外配合外部ID生成规则保证一致性。实践中的推荐做法是:把routing值同时冗余存储为一个普通字段并加上term查询条件,这样即使某些链路忘了传routing导致广播查询,结果依然正确,只是性能退化而不会出错。
总结一下使用routing的核心清单:写入、查询、更新、删除四类操作保持routing一致;评估数据倾斜风险并预留打散方案;查询条件中冗余routing字段兜底;规划好分片数量避免后期reindex。在多租户系统、用户维度的日志检索、按组织隔离的数据平台中,routing配合索引别名和filter,往往能构建出既快又清晰的隔离架构,值得在生产环境中认真落地。
Elasticsearch routing路由字段分片查询修改时间:2026-09-01 05:45:00