Elasticsearch 从 0 到 1(六):性能优化与生产环境注意事项
能跑通 ES 搜索接口只是第一步。
真正上线后,还会遇到这些问题:
- 写入慢;
- 查询慢;
- 深分页慢;
- 集群 yellow;
- 磁盘占用高;
- Mapping 设计不合理;
- 分片太多或太少;
- 搜索结果不稳定;
- MySQL 和 ES 延迟同步。
这篇整理 ES 入门阶段最值得先掌握的性能和生产注意事项。
前文可以先看:
分片不是越多越好
分片可以让数据分散到不同节点上,但分片也有管理成本。
分片太少:
- 单个分片太大;
- 查询和写入扩展能力有限;
- 数据分布不够灵活。
分片太多:
- 集群元数据变大;
- 查询需要合并更多分片结果;
- 内存和文件句柄消耗增加;
- 管理成本上升。
开发环境可以简单一点,生产环境要结合数据量、节点数、增长速度来设计。
初学阶段先记住:
分片数量要提前规划,不要为了“看起来能扩展”随便开很多。
副本的作用
副本主要有两个作用:
- 提高可用性;
- 分担读请求。
如果只有一个节点,却设置了 1 个副本,副本没地方放,集群健康状态可能是 yellow。
开发环境单节点可以设置:
{ "number_of_replicas": 0}生产环境通常至少 1 个副本。
批量写入用 Bulk
大量写入时不要一条一条 index。
应该使用 Bulk API。
BulkRequest.Builder br = new BulkRequest.Builder();
for (ProductDocument doc : documents) { br.operations(op -> op.index(idx -> idx .index("products") .id(String.valueOf(doc.getProductId())) .document(doc) ));}
BulkResponse response = client.bulk(br.build());批量大小不要无限大。
可以从几百到几千条试起,结合文档大小、网络、ES 压力来调。
写完要检查失败项,不要只看请求成功。
refresh 不要乱调
ES 写入后不是立刻就能被搜索到。
它有 refresh 机制,默认会周期性刷新,让新写入的数据变得可搜索。
如果每次写入都强制 refresh:
POST /products/_refresh会增加系统压力。
一般搜索场景可以接受短暂延迟,不要为了“立刻搜到”频繁手动 refresh。
如果是测试环境,需要马上验证,可以临时 refresh。生产环境要谨慎。
深分页问题
普通分页:
{ "from": 0, "size": 10}浅分页没问题。
但深分页很危险:
{ "from": 100000, "size": 10}ES 需要在各个分片上取出大量数据,再合并排序,开销很大。
解决思路:
- 限制最大页数;
- 使用
search_after; - 使用滚动或 PIT 处理大批量遍历;
- 业务上避免用户跳到特别深的页。
商品搜索页一般不应该让用户无限翻到几万页。
排序字段要设计好
排序字段最好是 keyword、数值、日期这类适合排序的字段。
不要随便对 text 字段排序。
比如:
"sort": [ { "price": "asc" }, { "sales": "desc" }]如果要按标题精确排序,可以用 title.keyword,前提是 Mapping 里设计了这个子字段。
filter 比 must 更适合筛选
商品搜索里,关键词放 must,筛选条件放 filter。
{ "bool": { "must": [ { "match": { "title": "蓝牙耳机" } } ], "filter": [ { "term": { "brand": "SoundGo" } }, { "range": { "price": { "gte": 100, "lte": 300 } } } ] }}filter 不参与相关性评分,更适合品牌、分类、价格、状态这类筛选。
控制返回字段
不要每次都返回完整文档。
可以用 _source 控制返回字段:
{ "_source": ["productId", "title", "price", "brand"], "query": { "match": { "title": "蓝牙耳机" } }}这样可以减少网络传输和反序列化成本。
尤其文档里有大字段时,收益明显。
慢查询要记录
搜索接口上线后,要记录慢查询。
建议日志里带上:
- keyword;
- filters;
- sort;
- pageNo;
- pageSize;
- took;
- total;
- requestId。
示例:
log.info("es search finished, keyword={}, pageNo={}, pageSize={}, took={}", keyword, pageNo, pageSize, response.took());慢查询不是只看 ES,也要看业务代码是否做了额外转换、远程调用或循环查询。
Mapping 设计影响性能
Mapping 设计不好,后面很难优化。
常见问题:
- 不需要搜索的字段也建了索引;
text和keyword用反;- 大字段参与搜索;
- 聚合字段被建成
text; - 对象数组没用
nested; - 字段过多导致 Mapping 膨胀。
如果字段只是展示,不需要搜索、过滤、排序,可以考虑关闭索引:
{ "description": { "type": "text", "index": false }}当然,关闭索引后就不能查询这个字段。
JVM 和内存
ES 是 Java 写的,对 JVM 和内存比较敏感。
生产环境要关注:
- JVM heap;
- GC;
- 文件系统缓存;
- 磁盘 IO;
- CPU;
- 网络;
- 查询线程池和写入线程池。
不要把所有内存都给 JVM heap,ES 还需要文件系统缓存。
具体参数要结合官方建议和实际机器调整,不要照抄固定值。
集群监控
至少要关注:
- 集群健康状态;
- 节点存活;
- 磁盘使用率;
- JVM heap 使用率;
- 查询耗时;
- 写入耗时;
- refresh / merge 压力;
- rejected 任务数;
- 分片状态。
如果线程池 rejected 增加,说明 ES 已经开始拒绝任务,业务侧要及时告警。
生产环境常见建议
整理一下常见建议:
- 索引和 Mapping 先设计再写入;
- 批量写入用 Bulk;
- 不要频繁手动 refresh;
- 限制深分页;
- 筛选条件尽量用 filter;
- 控制返回字段;
- 建立慢查询日志;
- 监控集群状态和线程池拒绝数;
- 不把 ES 当核心交易数据库;
- 同步失败要有补偿机制。
这一篇先记住什么
ES 性能优化不是只调一个参数,而是索引设计、查询方式、写入方式、集群资源一起看。
入门阶段先抓住:
- 分片不是越多越好;
- 副本提高可用性和读能力;
- 批量写入用 Bulk;
- 深分页要限制;
- 查询字段和排序字段要提前设计;
- filter 适合筛选;
- 搜索接口要记录慢查询;
- 生产环境必须监控集群状态。
下一篇用电商搜索把前面内容串起来:
参考
If this article helped you, please share it with others!
Some information may be outdated






