如果你平时负责维护 Elasticsearch(ES)集群,肯定遇到过这样的场景:业务说查询扛不住了,第一反应是哪台机器 CPU 打满,第二反应是加节点。而在 Serverless 环境下,加节点这件事变得既方便又昂贵,于是一个很自然的念头就冒出来了——不是有副本吗?让 ES 副本去分担读流量,这不就是现成的负载均衡吗?
这个思路在原理上完全成立。ES 的副本分片(replica shard)确实能承接读请求,只要把副本数和节点分布规划好,查询压力就能被摊到多台机器上。但真正落地时你会发现,Serverless 的弹性伸缩、计费模式、分片调度都会和副本机制产生一系列冲突,处理不好,轻则副本一直 unassigned,重则集群性能比原来更差。这篇文章就围绕“Serverless 中用于负载均衡的 ES 副本”这个命题,把原理、配置、踩坑一次讲透,适合准备把 ES 迁到 Serverless 平台、或者正在优化现有 ES 读能力的团队参考。
1. 先把概念拆干净:ES 副本凭什么能做负载均衡
1.1 主分片与副本分片的职责分工
很多刚接触 ES 的人会把“副本”理解成简单的数据备份,这个理解没错,但不够完整。ES 里一个索引的数据会拆成多个分片(shard),每个分片本质上是一个完整的 Lucene 索引。分片分为主分片(primary shard)和副本分片(replica shard)。主分片负责接收写入请求,副本分片从主分片同步数据,主要负责两件事:一是高可用,主分片所在节点挂了,副本可以被提升为新的主分片;二是分摊读压力,查询请求可以打到任意一个分片副本上,而不必每次都访问主分片。
这里的关键点在于“读请求可以打到副本上”。因为 ES 的查询分发机制会把一次搜索请求同时发到目标分片的所有可用副本上,而不是只发给主分片。也就是说,如果你有一个主分片和两个副本分片,分布在不同节点上,那么一次查询会被拆成三份并行执行,每个节点只处理自己持有的那份数据,最后在协调节点汇总。这就是副本参与负载均衡的基本原理。
用大白话讲:主分片就像项目的正式版本,只有它能被编辑;副本就像分发给团队成员的只读拷贝,大家看自己的拷贝干活,不用挤在一起抢正式版。你的正式版只有一份,但只读拷贝可以有很多份,分到不同人手里,效率自然就上来了。
理解这个分工之后,你就明白了为什么副本数是可以动态调整的。主分片数量在创建索引时一旦确定就改不了,需要 reindex 才能调整;而副本数量随时可以改,一条命令就能把 1 个副本变成 2 个或 3 个。这个特性对 Serverless 环境尤其重要,因为你可以在流量波峰临时上调副本数,波谷再降回来,代价只是等待分片复制完成的那几分钟。
1.2 一次搜索请求是如何被“扇出”到副本的
要说清楚副本怎么帮 ES 实现负载均衡,得先看一次完整查询的路径。假设客户端向集群发了一个 GET /products/_search 请求,请求先到达某个节点,这个节点可能是任意一个节点,因为 ES 集群中每个节点默认都能接收请求,节点之间可以互相转发。接收请求的这个节点,在这一轮查询里就充当了协调节点(coordinating node)。
协调节点拿到查询后,并不会自己把整个索引都搜一遍,而是先根据索引的分片配置,算出这个索引一共有多少个分片,然后向每个分片的主分片和所有副本分片发起内部查询请求。ES 的默认搜索行为是“所有分片都查,结果合并”,也就是说,一次查询实际是并行发给了索引的全部分片(包括副本),每个分片只负责查自己那部分数据,返回局部排序后的 top N 结果,协调节点再把所有局部结果合并、排序、分页,最终返回给客户端。
这个过程在 ES 里叫“扇出”(fan-out)。正是这种扇出机制,让 ES 天然具备了分布式并行查询的能力。当一个查询进来时,如果索引有 16 个主分片、每个主分片有 1 个副本,那么这 16 个分片对(shard copy)分散在多个节点上,查询就会被均匀地打给所有分片副本。从负载均衡的角度看,ES 自动帮你做了最粗粒度的流量分散——不管请求落在哪个节点,最终计算压力都会被摊到全集群。
这里有个细节值得注意:ES 在分发查询时,对每个分片对只会选择一个副本去查,不会同时查主分片和它的副本,否则结果就重复了。默认情况下,ES 会尽量选择“本地优先”的副本,也就是和协调节点在同一台机器上的那个副本,这样可以节省网络开销。所以如果你在 A 节点上发起查询,而某个分片的副本恰好也在 A 节点,那么这次查询就只用本地磁盘,不走网络,性能最好。
1.3 “等开销负载均衡”这句话应该怎么理解
热词里有一句“等开销负载均衡”,这句话看起来有点绕,但在 ES 副本场景里非常关键。先说结论:所谓等开销,指的是当每个分片上的查询计算量、IO 开销基本相等时,集群整体的负载分布才是最均衡的。
ES 默认的分片分配策略目标是尽量让分片在节点间均匀分布,但它并不保证每个分片的“热度”一样。如果数据写入时主分片的 hash 路由导致部分分片数据特别多,或者某些查询条件大量命中某几个热点分片,那么即使所有节点上的分片数量看起来一样,实际 CPU、内存开销也可能相差很大。副本在这里能做的是“复制数据”,而不是“重新分配数据的冷热度”。你把副本加到 3 个,如果热点分片还是那个热点分片,该热的节点照样热。
所以“等开销”的隐含前提是:分片大小要均匀、节点规格要一致、访问模式不能严重倾斜。在这个前提下,副本越多,参与计算的并行度越高,每个节点承担的查询量越接近,负载均衡的效果才越理想。反过来,如果数据倾斜严重,加副本只会放大问题,因为副本同步还会额外消耗内网带宽和磁盘 IO。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Serverless 才是真正的试金石:副本策略面临的四重矛盾
2.1 弹性伸缩与分片漂移的博弈
Serverless 平台最大的特点就是弹性伸缩,节点按照 CPU、内存、请求量等指标自动增加或减少。听起来很美,但对于 ES 这种有状态系统来说,自动伸缩是一把双刃剑。
当集群扩容,新的数据节点加入后,ES 的 rebalancer 会自动把部分分片迁移到新节点,以实现数据平衡。这本是好事,但在 Serverless 环境中,扩容事件是平台自动触发的,你可能在毫无预兆的情况下看到大量分片同时在迁移,内网带宽瞬间被占满,查询延迟飙高。问题更严重的是缩容。当平台检测到负载下降,会自动下线部分节点,此时必须先把这些节点上的分片迁走。如果副本数设置得不够合理,比如副本数为 0,那节点一下线,主分片就直接丢失,集群可能直接变红。
我的建议是,在 Serverless 平台上的 ES 集群,至少要保证“节点数大于副本数 + 1”。这句话的意思是,假设你想要集群能承受同时挂掉一个节点而不丢数据,那么至少得有 2 个节点、1 个副本;能承受挂掉两个节点,就得有 3 个节点、2 个副本。缩容策略里也要加一道防线,例如设置最小节点数,避免平台在流量低谷把所有节点缩到只剩一个,然后副本再多也白搭。
2.2 数据冗余带来的成本与利用率问题
Serverless 的计费模型通常对资源消耗非常敏感,按秒计费、按用量计费,这意味着 ES 副本带来的数据冗余会直接转化为账单上的数字。
举个具体的例子:假设你的索引数据量是 1TB,每个节点配了 500GB 数据盘。如果副本数设为 0,那么最少需要 2 个节点就能装下全部数据;但如果副本数设为 1,总数据量变成 2TB,至少需要 4 个节点;副本数设为 2,就需要 6 个节点。在 Serverless 环境中,多出来的这些节点,多出来的 CPU、内存、存储,统统都是成本。
所以我一直强调,副本不是免费的负载均衡。它的本质是用存储和资源换可用性和读性能。上线前最好先算一笔账:读 QPS 到底有多少,单节点能扛多少,瓶颈是在 CPU 还是在磁盘 IO,然后反推需要几个副本。如果只是读请求多、数据量不大,通过副本扩展读是划算的;如果数据量本身就很大,副本翻倍的存储成本几乎不可接受,这时候就得考虑加协调节点、加查询缓存,或者把冷数据挪到数据仓库,而不是无脑堆副本。
2.3 只读副本的局限:写入路径仍是单点瓶颈
副本能救读,但救不了写。所有写入请求都会落在主分片上,主分片写完后再把数据同步到所有副本,副本本身不接受写请求。所以如果你的瓶颈是写入,比如日志场景每秒几万条 ingest,那加副本不仅没有帮助,反而会因为同步开销让主分片更慢。
这个道理在 Serverless 场景下经常被忽略。有些人看到集群 CPU 高就去加副本,结果发现写入延迟不降反升,其实就是因为副本同步占用了额外的 CPU 和 IO。写入瓶颈的正确解法是增加主分片数量、优化写入路径、使用 bulk 批量写入、甚至引入消息队列做削峰填谷。副本只负责读扩展,这个边界必须清晰。
2.4 多可用区部署带来的网络开销
Serverless 平台通常要求你跨可用区部署应用,以提高容灾能力。ES 副本的分布策略天然适合跨可用区部署,你可以通过 index.routing.allocation.awareness.attributes 配置节点属性,让主分片和副本错开可用区。这样做的好处是某个可用区整体故障时,另一个可用区的副本仍然可以继续服务。但代价是跨可用区的数据同步延迟和带宽消耗会被放大。
副本同步是实时异步的,主分片每次写入后都要把增量数据发给所有副本。跨可用区网络的延迟通常比同机房高 1 到 3 毫秒,数据量大时这部分开销很可观。查询如果默认本地优先,同一可用区内的查询通常还是走本地副本,跨区网络压力主要发生在写入同步和分片迁移阶段。所以跨可用区部署时,建议重点关注集群的 network 指标,同时评估副本数,不能为了高可用无限增加副本,否则带宽先撑不住。
3. 从零落地:Windows 环境部署 ES 集群与副本负载均衡配置
3.1 Windows 下载安装与启动要点
很多团队会在本地用 Windows 机器做 ES 开发和验证,这里把步骤完整过一遍。首先去 Elastic 官网下载对应版本的 zip 包,以 8.x 和 9.x 为例,下载 windows x86_64 版本就行,解压出来是一个 elasticsearch-9.0.4 这样的目录。
有两个容易踩的坑。第一,目录名不要带空格,不要解压到 C:\Program Files 这种路径下,否则后续运行脚本很可能因为路径解析问题报错。第二,8.x 起 ES 默认内置了 JDK,不需要单独装 Java,但如果你希望用自己的 JDK,需要设置 JAVA_HOME 环境变量,并且要保证版本满足兼容性要求。
启动之前先改两个文件。第一个是 config/jvm.options,这里设置 JVM 堆内存,修改 -Xms4g 和 -Xmx4g。堆内存不是越大越好,一般建议设成物理内存的一半,并且不要超过 31GB,超过 31GB 会关闭压缩指针,反而浪费内存。第二个是 config/elasticsearch.yml,至少要确认集群名和节点名,比如:
yaml复制cluster.name: es-serverless-cluster
node.name: node-1
network.host: 0.0.0.0
discovery.seed_hosts: ["node-1:9300", "node-2:9300", "node-3:9300", "node-4:9300"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
我这里写的是一个 4 节点集群的种子配置,如果你只是单机验证,discovery.seed_hosts 可以留空,cluster.initial_master_nodes 写成自己的节点名就行。启动方式非常简单,在解压目录下执行:
bash复制bin\elasticsearch.bat
看到 started 日志就说明启动成功。浏览器访问 http://localhost:9200,如果开启了安全认证,会返回一个 JSON 对象和输入用户密码的提示。8.x 默认开启安全,第一次启动时控制台会打印 elastic 用户的初始密码,一定要复制保存,丢了只能重置。
3.2 集群节点配置与角色划分
继续说集群配置。ES 节点可以按角色划分职责,在 elasticsearch.yml 中使用 node.roles 指定。常见角色有 master(负责集群状态管理)、data_hot(热数据节点,保存近期高频访问的数据)、data_content(一般内容节点,可以放副本分片)、ingest(负责 ingest pipeline 数据预处理)、以及不设 data 角色的协调节点。
在副本负载均衡方案里,我推荐把节点角色错开,实现读写路径的物理隔离。例如开两个节点专门当协调节点,所有客户端查询都打到这两个节点上,这两个节点自己不存数据,只负责把请求转发给数据节点,再合并返回结果。这样协调节点的计算资源不会被数据存储挤占,查询合并能力的上限更高。
一个简化的 4 节点配置示例:
yaml复制# node-1, node-2
node.roles: [master, data_hot]
# node-3
node.roles: [data_content]
# node-4
node.roles: [data_content]
这样设置后,主分片尽量落到 data_hot 节点,副本可以手动分配到 data_content 节点,查询流量优先找本地的副本,整个集群的读写路径就清晰了。当然,生产环境不一定非要这样分,但理解角色拆分的思路对后续排查性能问题非常有帮助。
3.3 创建索引时设置副本数
副本数可以动态调整,但主分片数必须提前想好。创建索引时建议显式指定分片和副本,避免使用默认值:
json复制PUT /products
{
"settings": {
"number_of_shards": 16,
"number_of_replicas": 2
},
"mappings": {
"properties": {
"title": { "type": "text" },
"price": { "type": "double" }
}
}
}
如果有很多索引都需要统一配置,建议用索引模板管理。模板的好处是,匹配前缀的索引创建时自动套用你设定的分片数、副本数、mapping、生命周期策略,省去人肉维护。
当业务流量变化时,调整副本数只需要一行命令:
bash复制PUT /products/_settings
{
"number_of_replicas": 3
}
这条命令发出后,ES 会异步创建 3 个副本分片并开始复制数据。这里要提醒:副本数的调整不需要重建索引,也不影响集群对外服务,但复制过程会占用网络和磁盘 IO,最好在流量低谷执行。
3.4 网关、F5 等外部负载均衡如何与副本策略配合
副本解决的是 ES 内部的数据层面均衡,但请求从客户端进入集群的入口也需要均衡。很多企业会使用 F5 这类硬件负载均衡设备,或者云平台上的网关服务。外部负载均衡的作用是把客户端流量均匀分发到多个 ES 节点,避免所有请求都打到同一个节点上。
配置外部负载均衡时,健康检查建议用 GET /_cluster/health 接口,检查返回的 HTTP 状态码是否为 200。如果集群是红色状态,健康检查接口也可以返回 503,外部负载均衡就能自动摘除异常节点。
这里有一个容易被忽视的问题:你让外部负载均衡打给哪些节点?如果只打给某个特定节点,这个节点的协调开销就会很大,副本再多也没用。正确做法是让流量尽量分散到多个节点,尤其是带协调角色的节点。在 Serverless 场景下,更推荐在入口处做读写分离:写请求路由到主分片所在的节点组,读请求路由到副本所在的节点组。虽然 ES 内部查询会自动扇出到全部分片,但从接入层提前分流,可以显著减少协调节点的压力波动。
4. 一个电商搜索场景的完整配置案例
4.1 场景建模与分片副本设计
用一个电商商品搜索场景来串一下前面的理论知识。假设商品索引数据量 500GB,写 QPS 大约 500,读 QPS 5000,数据每天增长。对搜索场景来说,这是一个非常典型的读多写少模型,非常适合用副本来扩展读能力。
分片数量怎么定?一个经验值是单分片控制在 20GB 到 40GB 之间,太大则单分片查询慢,太小则分片数量过多、协调节点合并开销大。500GB 数据量,16 个主分片是合理选择。副本数方面,我建议线上环境至少 1 个副本,满足日常容灾需求;如果读 QPS 确实很高,且数据节点 CPU 还有余量,可以上调到 2 个副本。下面是不同副本数的对比:
| 副本数 | 数据总量 | 最少节点数 | 适用场景 |
|---|---|---|---|
| 0 | 500GB | 2 | 临时分析索引、可重建数据 |
| 1 | 1TB | 4 | 生产环境默认,兼顾可用性与成本 |
| 2 | 1.5TB | 6 | 读 QPS 很高、需要跨可用区容灾 |
| 3 | 2TB | 8 | 极端读需求,但存储成本显著上升 |
节点规格按 32GB 内存、500GB 数据盘粗算,16 个主分片加 16 个副本分片,每个节点分摊约 8 个分片,每个分片对应大约 64MB 的堆内缓存,资源压力还在合理范围。
4.2 写入路径与查询路径的分流
在这个电商场景里,商品信息的写入集中在后台管理系统,搜索流量则集中在用户端。建议在入口网关层把这两个流量分开。写入请求通过写入口进入集群,先经过一个 ingest pipeline,做分词、字段规范、价格单位换算等预处理,然后落主分片。读取请求走读入口,可以附加一层应用缓存,比如把热门关键词、热门商品 ID 的结果缓存在 Redis 里,命中缓存就不必打到 ES。
查询侧还有一个容易被漏掉的问题:深分页。很多业务人员喜欢在页面上点击最后一页,这对 ES 很不友好。ES 的 from + size 翻页越深,协调节点需要合并的数据量越大,尤其是分片多、副本多的时候,开销会被放大好几倍。在电商搜索中,建议限制用户最多翻 100 页,或者改用 search_after + PIT 实现游标翻页。这个优化有时候比加副本更有效。
4.3 扩展到 OLAP 聚合场景时的注意事项
ES 也经常被用来做轻量级 OLAP,比如订单分析、销售趋势统计。这时候聚合查询会扫描大量数据,结果集可能在协调节点内存中膨胀。很多人误以为副本越多,聚合算得越快,实际上聚合开销主要取决于待扫描的数据量,而不是参与计算的副本数量。副本增加只意味着同样一份数据被扫描更多份,协调节点合并内存翻倍,性能反而更差。
所以做 OLAP 场景要注意几点:第一,聚合查询要做分类,高频且轻量的聚合可以放开,重量级聚合要限量限时;第二,合理设置 index.max_result_window,避免一次聚合返回过多桶;第三,能提前过滤的数据就加过滤条件,比如只聚合最近 7 天;第四,近似聚合如 cardinality 可以显著降低内存消耗,误差在可接受范围内时尽量使用。如果 OLAP 查询特别重,建议建独立的聚合索引,保持较低的副本数,甚至副本为 0,从源头消除副本带来的额外扫描成本。
5. 常见故障与排查技巧实录
5.1 副本一直 unassigned,怎么定位
副本数量设置好了,但集群状态一直是黄色,说明有副本分片没有被分配。最常见的几个原因:节点总数小于副本数加一,磁盘水位达到高水位线,分配规则冲突,或者分片恢复卡住。
第一步先看分配解释接口:
bash复制GET /_cluster/allocation/explain?pretty
这个接口会直接告诉你当前未分配分片的具体原因。比如 disk watermarks 表示磁盘空间不足,too many shards per node 表示单节点分片数超限,allocation filtering 表示你的节点标签过滤规则阻止了分配。定位到原因后再对症处理:磁盘不足就清理数据或加节点,分片数超限就调整单节点分片数上限,过滤规则冲突就检查 index.routing.allocation 配置。
5.2 集群变黄变红时先看什么
生产环境最吓人的就是集群状态变成红色,意味着有主分片丢失。这时候不要慌,也不要贸然执行 reroute 强制恢复。红状态第一步看集群健康详情,第二步看未分配分片原因,第三步检查节点是否掉线。如果只是节点掉线,网线拔了又插上,ES 会自动恢复;如果节点彻底没了,并且主分片没有副本,那只能从快照恢复。
这里有一个重要原则:不要用 cluster.routing.allocation.enable 关掉分配后,再手动强制分配一个空的副本分片。那样做的结果是一个无法恢复的空分片被提升为主分片,原来的数据等于被覆盖了。副本分片缺失时,ES 会重新复制,不必干预;主分片缺失且无副本时,唯一可靠的办法是快照恢复。
5.3 热点分片与查询性能毛刺
副本均衡有时候看着分片数很平均,但查询还是慢,很可能是热点分片问题。举个例子,某个索引有 16 个分片,但某一种商品的搜索特别热门,而这个商品的数据恰好只分布在一个分片上,那这个分片所在的节点压力会明显高于其他节点。
排查热点可以用:
bash复制GET /_cat/shards?v&s=store.size:desc
GET /_nodes/hot_threads?pretty
第一句看分片大小是否有明显倾斜,第二句看具体节点的线程栈。如果确实是数据倾斜导致的热点,解决办法通常是 reindex 到新的主分片数量更多的索引,让热点数据分散到更多分片。这个操作看起来麻烦,但对于真正的热点问题,它比单纯加副本有效得多。
5.4 安全认证、跨域与内网客户端连接问题
8.x 及以上版本默认开启 xpack.security,客户端连接时会出现 SSL 握手错误,或者提示没有认证。很多第一次上手的人都会在这里卡住。检查点有两个:一个是客户端连接地址必须用 https://,一个是需要配置用户名密码或者 API Key。
如果你使用的是浏览器工具查看数据,比如 Elasticvue 这类插件,还需要在 elasticsearch.yml 里开启跨域配置:
yaml复制http.cors.enabled: true
http.cors.allow-origin: "*"
这只是本地开发用,生产环境建议把 allow-origin 限定到具体的前端域名。另外,企业内部经常遇到使用 Kerberos 或 SPNEGO 做单点登录的情况,这些认证协议配置链路比较长,建议先用用户密码方式把集群跑通,再逐步叠加单点认证,不要一开始就一起上,否则排错会很痛苦。最后一个小提醒:不要用 elastic 超级用户跑业务逻辑,按索引粒度创建只读或读写的最小权限用户,避免误删数据。
6. 一些没必要重复踩的坑和个人体会
最后分享几个我自己的体会。第一,副本不是万能的。很多人问我加了 2 个副本查询还是很慢,结果排查下来要么是索引 mapping 设计不合理,导致某些字段使用了超高成本的查询,要么是 JVM 堆太小频繁 GC,要么是分片数据倾斜,加副本根本解决不了这些根因。
第二,慎重让 Serverless 平台自动管理 ES 集群的缩容。ES 是有状态系统,节点下线前需要完成分片迁移,这个过程耗时可能长达几十分钟。平台如果只看 CPU 指标就缩容,很容易在迁移还没完成时就切断节点,造成数据不可用。能手动控制缩容节奏,就不要全自动。
第三,副本数应该是动态策略,而不是一成不变的配置。你可以根据监控指标制定规则:读 QPS 持续 10 分钟超过阈值,就把副本数从 1 调到 2;流量回落后,再调回 1。这样既保证了体验,又不至于让存储成本一直居高不下。
这套思路用在 Serverless 环境的 ES 上,核心就一句话:用副本做读扩展和容灾没问题,但要想清楚权衡点在哪里,不要把它当成免检的负载均衡器。想清楚了,副本就是你手里最趁手的工具。
