Elasticsearch 生产环境全栈最佳实践:从架构设计到故障排查一站式落地指南
前言
Elasticsearch 作为一款分布式搜索引擎,被广泛应用于全文检索、日志分析、运维监控、电商搜索等核心业务场景。但将其从测试环境落地到生产环境,需要兼顾性能、稳定性、安全性、可运维性等多维度的要求,稍有不慎就可能出现集群宕机、数据丢失、查询超时等严重故障。
本文基于生产环境实战经验,整理了 Elasticsearch 落地的全流程最佳实践,覆盖集群架构设计、JVM 配置优化、索引设计、读写性能调优、监控告警、数据备份、安全配置及常见故障排查全链路,所有配置与代码均可直接复用,帮助开发者避坑,打造高可用、高性能的 ES 集群。
一、集群架构设计
合理的架构设计是 ES 集群稳定运行的根基,核心在于节点角色的合理拆分与硬件资源的精准匹配,避免所有角色混部导致的资源争抢与故障扩散。
1.1 节点角色规划
ES 集群中的节点可承担不同的角色,各司其职,降低单节点压力,提升集群稳定性。各角色的核心职责与配置如下:
| 角色 | 核心说明 | 核心配置 |
|---|---|---|
| Master 节点 | 集群管理节点,负责集群状态维护、分片分配、索引创建删除等元数据操作,不处理业务读写请求 | node.master: true, node.data: false |
| Data 节点 | 数据存储节点,负责文档存储、数据写入、查询与聚合计算,是集群的核心资源消耗节点 | node.master: false, node.data: true |
| Coordinating 节点 | 协调节点,负责客户端请求接入、请求分发、结果汇总与聚合,承接客户端流量,降低 Data 节点压力 | node.master: false, node.data: false |
| Ingest 节点 | 数据预处理节点,负责写入前的数据转换、过滤、格式化等操作,降低 Data 节点的写入压力 | node.ingest: true |
1.2 不同规模集群推荐架构
根据业务数据量与请求量级,可选择对应的集群架构,避免资源浪费或性能不足:
bash
# 小型集群(3节点,数据量<1TB,QPS<1000)
- 3个节点:同时承担 Master + Data + Coordinating 角色
# 中型集群(6-9节点,数据量1-10TB,QPS 1000-10000)
- 3个专用 Master 节点(仅负责集群管理,不处理业务请求)
- 3-6个 Data 节点(负责数据存储与读写)
- 可选:2个 Coordinating 节点(承接客户端流量,做请求负载)
# 大型集群(10+节点,数据量>10TB,QPS>10000)
- 3个专用 Master 节点
- 多个 Data 节点(按数据量线性扩展)
- 2-3个专用 Coordinating 节点
- 可选:专用 Ingest 节点(写入量大、预处理逻辑复杂场景)
注意:Master 节点必须设置奇数个(推荐 3 个),避免脑裂问题;生产环境严禁单节点集群,至少 3 节点保证高可用。
1.3 硬件配置建议
硬件配置直接决定集群的性能上限,需根据节点角色匹配对应资源,核心原则是:优先 SSD 磁盘、保证内存充足、网络低延迟。
bash
# Master 节点(轻量型,仅管理集群)
CPU: 2-4核
内存: 8-16GB
磁盘: 50-100GB SSD(仅存储集群元数据,无需大容量)
# Data 节点(核心资源节点,性能核心)
CPU: 8-16核
内存: 32-64GB
磁盘: 1-4TB SSD(机械盘会严重降低读写性能,生产环境优先SSD)
网络: 万兆网卡(分片同步、数据传输依赖高带宽低延迟网络)
# Coordinating 节点(流量接入与结果汇总)
CPU: 4-8核
内存: 16-32GB
磁盘: 100GB(仅存储日志,无需大容量)
二、JVM 配置优化
Elasticsearch 基于 Java 开发,JVM 配置直接决定集群的稳定性与性能,核心是堆内存的合理分配与垃圾回收器的优化。
2.1 核心内存分配原则
ES 的内存分为两部分:JVM 堆内存(用于 ES 核心逻辑)、操作系统堆外内存(用于 Lucene 索引文件缓存,是高性能的关键)。核心分配原则如下:
- 堆内存必须设置为物理内存的 50%,剩余 50% 留给操作系统做 Lucene 索引缓存,严禁堆内存占比超过 50%;
- 堆内存最大不能超过 32GB,超过 32GB 会失去 JVM 压缩指针(Compressed OOPs)优化,内存利用率大幅下降,反而会降低性能;
- Xms 和 Xmx 必须设置为相同的值,避免 JVM 运行时动态调整堆内存大小,减少 GC 停顿。
示例配置(jvm.options 或 elasticsearch.yml):
yaml
# 64GB 物理内存的最优配置
-Xms31g
-Xmx31g
# 32GB 物理内存配置
-Xms16g
-Xmx16g
2.2 GC 配置优化
ES 7.x 及以上版本默认使用 G1 垃圾回收器,生产环境无需更换回收器,仅需优化核心参数,控制 GC 停顿时间:
yaml
# 启用 G1 GC(7.x+ 默认开启,无需修改)
-XX:+UseG1GC
# 设置最大 GC 停顿时间目标,默认200ms,可根据业务调整
-XX:MaxGCPauseMillis=200
# 触发并发 GC 的堆内存占用阈值,默认45%,写入量大可适当上调
-XX:InitiatingHeapOccupancyPercent=45
# GC 日志配置,生产环境必须开启,用于故障排查
-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
三、索引设计优化
索引是 ES 数据存储的核心单元,合理的索引设计能从根本上提升读写性能,降低资源消耗,核心分为分片策略、Mapping 优化、索引模板三部分。
3.1 分片策略
分片是 ES 分布式存储的最小单元,主分片数量在索引创建后无法修改,副本分片可动态调整,核心最佳实践如下:
- 单个分片的最优大小为 20-50GB,过小会导致分片数量过多,集群元数据管理开销大;过大会导致分片迁移、恢复速度慢,影响集群稳定性;
- 主分片数量计算公式:
主分片数 = 数据总量 / 单分片最优大小(推荐30GB); - 副本数根据可用性需求设置,生产环境至少 1 个副本,核心业务可设置 2 个副本,副本数不能超过 Data 节点数 - 1;
- 写入量大的场景,可适当调大
refresh_interval(默认 1s),降低段合并频率,提升写入性能。
索引分片配置示例:
json
PUT /my_index
{
"settings": {
"number_of_shards": 5,
"number_of_replicas": 1,
"refresh_interval": "30s"
}
}
3.2 Mapping 优化
Mapping 定义了文档的字段类型、索引规则、分词方式等,不合理的 Mapping 会导致存储空间浪费、查询性能下降,核心优化要点如下:
- 不需要搜索的字段,设置
"index": false,关闭索引,减少存储空间与写入开销; - 不需要分词、不需要相关性评分的字段(如状态、分类、ID 等),使用
keyword类型,而非text类型; - 金额类浮点字段,使用
scaled_float代替float/double,通过缩放因子转为整数存储,大幅节省存储空间; - 同时需要分词搜索与精准匹配的字段,使用
fields实现多字段索引,兼顾不同查询场景。
完整 Mapping 优化示例:
json
PUT /products
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"description": {
"type": "text",
"index": false
},
"tags": {
"type": "keyword"
}
}
}
}
3.3 索引模板
对于日志、监控等时序类场景,会按天 / 周创建索引,通过索引模板可统一管理索引的 settings 与 mappings,避免重复配置,保证规范统一。
索引模板示例(日志场景):
json
PUT _index_template/logs_template
{
"index_patterns": ["logs-*"],
"template": {
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"index.lifecycle.name": "logs_policy"
},
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"level": {
"type": "keyword"
},
"message": {
"type": "text"
},
"service": {
"type": "keyword"
}
}
}
}
}
说明:所有匹配
logs-*规则的索引,创建时会自动应用该模板的配置,无需手动设置。
四、查询性能优化
查询是 ES 最核心的业务场景,不合理的查询会导致集群 CPU 飙升、查询超时,甚至触发 OOM,核心优化实践如下。
4.1 使用 Filter 代替 Query
ES 中 Filter 与 Query 的核心区别:
- Query:会计算文档与查询条件的相关性评分,不支持缓存;
- Filter:不参与评分,仅过滤符合条件的文档,结果会被 ES 缓存,后续相同查询可直接命中缓存,性能提升数十倍。
正反示例(Java 客户端):
java
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchSourceBuilder;
// ❌ 不好的做法:直接使用 Query,无缓存,性能差
SearchRequest request = new SearchRequest("products");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("status", "active"));
// ✅ 好的做法:使用 BoolQuery + Filter,利用缓存,性能最优
sourceBuilder.query(
QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("status", "active"))
);
request.source(sourceBuilder);
4.2 分页优化
ES 默认的 from + size 分页存在严重的深度分页问题,当 from 超过 10000 时,性能会急剧下降,甚至触发 OOM。
核心原因:from=10000, size=10 时,ES 需要在每个分片上读取前 10010 条数据,协调节点汇总所有分片的数据后排序,再取第 10000-10010 条数据,内存开销与耗时随分页深度线性增长。
优化方案如下:
java
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.search.sort.SortOrder;
// ❌ 不推荐:深度分页,性能极差
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.from(10000);
sourceBuilder.size(10);
// ✅ 方案1:Scroll API,适合全量数据导出、离线批量处理
SearchRequest searchRequest = new SearchRequest("products");
searchRequest.scroll(TimeValue.timeValueMinutes(1)); // 滚动窗口有效期
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(1000); // 单次滚动读取条数
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
String scrollId = response.getScrollId();
// 持续滚动获取下一批数据
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(TimeValue.timeValueMinutes(1));
SearchResponse nextResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
// ✅ 方案2:Search After(推荐),适合前端实时分页,无深度分页问题
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(10);
sourceBuilder.sort("id", SortOrder.ASC); // 必须有唯一排序字段
sourceBuilder.searchAfter(new Object[]{lastId}); // 上一页最后一条数据的排序值
4.3 聚合优化
聚合查询是 ES 高 CPU 开销的操作,尤其是大数量、多层级的聚合,核心优化原则是:限制聚合桶数量,避免全量数据聚合。
优化示例:
java
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.CompositeAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.TermsValuesSourceBuilder;
// ✅ terms 聚合优化:限制桶数量,避免返回过多结果
AggregationBuilder aggregation = AggregationBuilders
.terms("categories")
.field("category.keyword")
.size(100) // 限制最终返回的桶数量
.shardSize(200); // 每个分片提前返回的桶数量,减少协调节点汇总开销
// ✅ 大量数据聚合:使用 composite 聚合,支持滚动分页聚合,避免内存溢出
CompositeAggregationBuilder compositeAgg =
AggregationBuilders.composite("my_buckets",
Arrays.asList(
new TermsValuesSourceBuilder("category")
.field("category.keyword")
)
).size(1000); // 单次聚合返回的桶数量
五、写入性能优化
大批量数据写入、日志实时采集等场景,写入性能是核心瓶颈,以下优化方案可大幅提升写入吞吐量,降低写入延迟。
5.1 批量写入
单条文档写入会产生大量的网络 IO 开销,生产环境必须使用 Bulk 批量写入,推荐单次批量写入的文档数为 1000-5000 条,单次批量请求大小不超过 10MB。
Spring Boot 批量写入完整实现:
java
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Service
public class ElasticsearchBulkService {
@Autowired
private RestHighLevelClient client;
// 单次批量提交的文档数,可根据文档大小调整
private static final int BULK_SIZE = 1000;
public void bulkIndex(List<Product> products) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
for (int i = 0; i < products.size(); i++) {
Product product = products.get(i);
IndexRequest request = new IndexRequest("products")
.id(product.getId().toString())
.source(convertToMap(product));
bulkRequest.add(request);
// 达到批量阈值,提交一次
if ((i + 1) % BULK_SIZE == 0) {
client.bulk(bulkRequest, RequestOptions.DEFAULT);
// 重置批量请求,避免重复提交
bulkRequest = new BulkRequest();
}
}
// 提交剩余不足阈值的文档
if (bulkRequest.numberOfActions() > 0) {
client.bulk(bulkRequest, RequestOptions.DEFAULT);
}
}
// 将对象转为 Map 结构,适配 ES 写入要求
private Map<String, Object> convertToMap(Product product) {
// 此处可根据业务实现对象转换,推荐使用 Jackson 序列化
return Map.of(
"id", product.getId(),
"name", product.getName(),
"price", product.getPrice(),
"created_at", product.getCreatedAt()
);
}
}
5.2 调整刷新间隔
ES 写入的文档需要经过 refresh 操作才能被搜索到,默认 refresh 间隔为 1s,频繁的 refresh 会产生大量的小段文件,触发频繁的段合并,严重影响写入性能。
大批量数据导入场景,可临时关闭 refresh,导入完成后恢复:
json
// 写入期间关闭 refresh,禁用自动刷新
PUT /my_index/_settings
{
"refresh_interval": "-1"
}
// 写入完成后恢复为30s,兼顾写入性能与搜索实时性
PUT /my_index/_settings
{
"refresh_interval": "30s"
}
5.3 临时禁用副本
副本是保证数据高可用的核心,但写入时,主分片写入完成后,需要同步到所有副本分片,会产生大量的网络与磁盘 IO,降低写入吞吐量。
全量数据初始化、大批量数据导入场景,可临时禁用副本,导入完成后恢复,写入性能可提升数倍:
json
// 大量数据导入时,临时禁用副本
PUT /my_index/_settings
{
"number_of_replicas": 0
}
// 导入完成后恢复副本,生产环境必须恢复为1及以上
PUT /my_index/_settings
{
"number_of_replicas": 1
}
六、监控与告警
生产环境必须建立完善的监控告警体系,提前发现集群隐患,避免故障发生,核心监控维度包括:集群健康状态、节点性能指标、慢查询日志。
6.1 集群健康监控
集群健康状态是最核心的监控指标,分为 GREEN、YELLOW、RED 三个等级:
- GREEN:所有主分片与副本分片均正常分配,集群完全健康;
- YELLOW:所有主分片正常分配,存在未分配的副本分片,集群可正常读写,可用性下降;
- RED:存在未分配的主分片,对应索引不可用,集群读写异常,必须紧急处理。
Spring Boot 定时监控集群健康状态实现:
java
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
@Slf4j
public class ElasticsearchMonitorService {
@Autowired
private RestHighLevelClient client;
// 每分钟执行一次集群健康检查
@Scheduled(fixedRate = 60000)
public void checkClusterHealth() throws IOException {
ClusterHealthRequest request = new ClusterHealthRequest();
ClusterHealthResponse response = client.cluster()
.health(request, RequestOptions.DEFAULT);
ClusterHealthStatus status = response.getStatus();
// 集群状态异常,触发告警
if (status == ClusterHealthStatus.RED) {
log.error("ES集群状态RED,存在不可用的主分片,业务已受影响");
// 此处接入企业微信、钉钉、短信等告警渠道
sendAlert("ES集群状态异常:RED,存在不可用的主分片");
} else if (status == ClusterHealthStatus.YELLOW) {
log.warn("ES集群状态YELLOW,存在未分配的副本分片,可用性下降");
} else {
log.info("ES集群状态正常:GREEN");
}
// 记录集群节点信息
log.info("集群节点总数:{},数据节点数:{}",
response.getNumberOfNodes(),
response.getNumberOfDataNodes());
}
// 告警发送实现
private void sendAlert(String message) {
// 接入对应告警渠道即可
}
}
6.2 节点性能指标监控
节点级别的性能指标,可提前发现资源瓶颈,核心监控指标包括:JVM 堆内存使用率、CPU 使用率、磁盘使用率。
Spring Boot 性能指标采集实现:
java
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.node.stats.NodeStats;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
@Slf4j
public class ElasticsearchMetricsService {
@Autowired
private RestHighLevelClient client;
public void collectMetrics() throws IOException {
// 采集所有节点的性能指标
NodesStatsRequest nodesStatsRequest = new NodesStatsRequest();
nodesStatsRequest.all();
NodesStatsResponse nodesStats = client.nodes()
.stats(nodesStatsRequest, RequestOptions.DEFAULT);
for (NodeStats nodeStats : nodesStats.getNodes()) {
String nodeName = nodeStats.getNode().getName();
// 1. JVM 堆内存使用率监控,阈值85%
long heapUsed = nodeStats.getJvm().getMem().getHeapUsed().getBytes();
long heapMax = nodeStats.getJvm().getMem().getHeapMax().getBytes();
double heapUsedPercent = (double) heapUsed / heapMax * 100;
if (heapUsedPercent > 85) {
log.warn("节点 {} 堆内存使用率过高:{:.2f}%,存在OOM风险",
nodeName, heapUsedPercent);
}
// 2. CPU 使用率监控,阈值80%
short cpuPercent = nodeStats.getOs().getCpu().getPercent();
if (cpuPercent > 80) {
log.warn("节点 {} CPU使用率过高:{}%,存在性能瓶颈",
nodeName, cpuPercent);
}
// 3. 磁盘使用率监控,阈值85%
long diskTotal = nodeStats.getFs().getTotal().getTotal().getBytes();
long diskFree = nodeStats.getFs().getTotal().getFree().getBytes();
double diskUsedPercent = (double) (diskTotal - diskFree) / diskTotal * 100;
if (diskUsedPercent > 85) {
log.warn("节点 {} 磁盘使用率过高:{:.2f}%,存在磁盘满风险",
nodeName, diskUsedPercent);
}
}
// 采集索引级别的统计信息
IndicesStatsRequest indicesStatsRequest = new IndicesStatsRequest();
IndicesStatsResponse indicesStats = client.indices()
.stats(indicesStatsRequest, RequestOptions.DEFAULT);
log.info("索引总数:{}", indicesStats.getIndices().size());
log.info("集群文档总数:{}", indicesStats.getTotal().getDocs().getCount());
log.info("集群总存储大小:{}GB",
indicesStats.getTotal().getStore().getSizeInBytes() / 1024 / 1024 / 1024);
}
}
6.3 慢查询日志
慢查询日志是定位查询性能问题的核心,生产环境必须开启,配置合理的阈值,记录耗时过长的查询与写入请求。
慢查询日志配置(elasticsearch.yml):
yaml
# 查询慢日志阈值配置
index.search.slowlog.threshold.query.warn: 10s
index.search.slowlog.threshold.query.info: 5s
index.search.slowlog.threshold.query.debug: 2s
# 写入慢日志阈值配置
index.indexing.slowlog.threshold.index.warn: 10s
index.indexing.slowlog.threshold.index.info: 5s
七、数据备份与恢复
生产环境必须建立完善的数据备份机制,避免误操作、集群故障导致的数据丢失,ES 官方推荐使用快照(Snapshot)方式进行数据备份,支持增量备份与快速恢复。
7.1 快照基础配置
快照需要先注册快照仓库,仓库类型支持共享文件系统、HDFS、阿里云 OSS、AWS S3 等,生产环境推荐使用共享文件系统或对象存储。
注意:使用文件系统类型仓库时,仓库路径必须是所有集群节点都能访问的共享存储(如 NFS),否则快照会失败。
快照仓库注册与基础操作:
json
// 1. 注册快照仓库
PUT /_snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/mount/backups/elasticsearch",
"compress": true // 开启压缩,节省存储空间
}
}
// 2. 创建快照,备份指定索引
PUT /_snapshot/my_backup/snapshot_1
{
"indices": "products,orders", // 指定要备份的索引,不填则备份所有索引
"ignore_unavailable": true, // 忽略不存在的索引,避免备份失败
"include_global_state": false // 不备份集群全局状态,避免恢复时覆盖集群配置
}
// 3. 查看快照状态
GET /_snapshot/my_backup/snapshot_1
// 4. 从快照恢复数据
POST /_snapshot/my_backup/snapshot_1/_restore
{
"indices": "products,orders", // 指定要恢复的索引
"ignore_unavailable": true,
"include_global_state": false
}
7.2 自动快照与过期清理
生产环境推荐使用定时任务实现自动快照,同时自动清理过期快照,避免磁盘空间被占满。
Spring Boot 自动快照实现:
java
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotRequest;
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsRequest;
import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.snapshots.SnapshotInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Service
@Slf4j
public class ElasticsearchBackupService {
@Autowired
private RestHighLevelClient client;
private static final String REPOSITORY_NAME = "my_backup";
private static final int RETENTION_DAYS = 7; // 快照保留7天
// 每天凌晨2点执行快照创建
@Scheduled(cron = "0 0 2 * * ?")
public void createSnapshot() {
try {
// 按日期生成快照名称,保证唯一性
String snapshotName = "snapshot_" +
LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
CreateSnapshotRequest request = new CreateSnapshotRequest();
request.repository(REPOSITORY_NAME);
request.snapshot(snapshotName);
request.indices("products", "orders"); // 指定要备份的索引
request.waitForCompletion(false); // 异步执行,不阻塞
CreateSnapshotResponse response = client.snapshot()
.create(request, RequestOptions.DEFAULT);
log.info("ES快照创建成功:{}", snapshotName);
} catch (Exception e) {
log.error("ES快照创建失败", e);
}
}
// 每天凌晨3点清理过期快照
@Scheduled(cron = "0 0 3 * * ?")
public void cleanOldSnapshots() {
try {
LocalDate cutoffDate = LocalDate.now().minusDays(RETENTION_DAYS);
GetSnapshotsRequest request = new GetSnapshotsRequest();
request.repository(REPOSITORY_NAME);
GetSnapshotsResponse response = client.snapshot()
.get(request, RequestOptions.DEFAULT);
for (SnapshotInfo snapshot : response.getSnapshots()) {
String snapshotName = snapshot.snapshotId().getName();
try {
// 解析快照名称中的日期
String dateStr = snapshotName.replace("snapshot_", "");
LocalDate snapshotDate = LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE);
// 删除超过保留期的快照
if (snapshotDate.isBefore(cutoffDate)) {
client.snapshot().delete(
new org.elasticsearch.action.admin.cluster.snapshots.delete.DeleteSnapshotRequest(
REPOSITORY_NAME, snapshotName
), RequestOptions.DEFAULT
);
log.info("过期快照已删除:{}", snapshotName);
}
} catch (Exception e) {
log.warn("快照名称解析失败,跳过:{}", snapshotName);
}
}
} catch (Exception e) {
log.error("ES快照清理失败", e);
}
}
}
八、安全配置
生产环境的 ES 集群严禁裸奔,必须开启安全配置,避免数据泄露、恶意篡改等安全风险,核心配置如下。
8.1 启用安全特性
ES 自带 X-Pack 安全插件,生产环境必须开启,启用账号密码认证与 SSL 传输加密。
安全配置(elasticsearch.yml):
yaml
# 开启X-Pack安全认证
xpack.security.enabled: true
# 开启节点间传输SSL加密
xpack.security.transport.ssl.enabled: true
# 开启HTTP请求SSL加密,客户端必须使用HTTPS访问
xpack.security.http.ssl.enabled: true
8.2 用户与权限管理
生产环境严禁使用 elastic 超级管理员账号接入业务,必须创建自定义角色与用户,遵循最小权限原则,仅分配业务所需的权限。
bash
# 1. 命令行创建用户,分配自定义角色
bin/elasticsearch-users useradd app_user -p your_secure_password -r app_role
json
// 2. 创建自定义角色,分配最小权限
PUT /_security/role/app_role
{
"cluster": ["monitor"], // 集群级权限:仅监控
"indices": [
{
"names": ["products", "orders"], // 仅允许访问指定索引
"privileges": ["read", "write"] // 索引级权限:仅读写,无删除、修改配置等权限
}
]
}
8.3 Java 客户端认证配置
开启安全认证后,Java 客户端必须配置账号密码与 SSL 认证,才能正常访问集群。
Spring Boot 客户端配置完整实现:
java
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient client() {
// 配置账号密码认证
final CredentialsProvider credentialsProvider =
new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials("app_user", "your_secure_password")
);
// 构建客户端,开启HTTPS,配置认证信息
RestClientBuilder builder = RestClient.builder(
new HttpHost("es-node-1", 9200, "https"),
new HttpHost("es-node-2", 9200, "https")
)
.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
);
return new RestHighLevelClient(builder);
}
}
九、常见问题处理
生产环境难免会出现各种异常,以下是最常见的三大问题的排查与解决方案,可直接复用。
9.1 集群状态 RED
核心原因 :集群中存在不可用的主分片,对应索引无法正常读写,业务受影响。排查与解决步骤:
json
# 1. 查看集群健康状态,确认异常索引
GET /_cluster/health?pretty
# 2. 查看未分配的分片,定位未分配原因
GET /_cat/shards?v&h=index,shard,prirep,state,unassigned.reason
# 3. 手动分配分片(仅当无法自动恢复时使用,accept_data_loss 会接受数据丢失风险)
POST /_cluster/reroute
{
"commands": [
{
"allocate_stale_primary": {
"index": "my_index",
"shard": 0,
"node": "node-1",
"accept_data_loss": true
}
}
]
}
9.2 内存溢出 OOM
核心现象 :节点频繁 Full GC、进程宕机、查询超时、日志中出现 OutOfMemoryError 错误。排查与解决步骤:
json
# 1. 查看节点JVM内存使用情况,定位高内存节点
GET /_nodes/stats/jvm
解决方案:
- 调整 JVM 堆内存配置,保证 Xms 与 Xmx 一致,不超过物理内存 50%,不超过 32GB;
- 优化慢查询,减少聚合桶数量,避免深度分页,使用 Filter 代替 Query 减少内存开销;
- 检查是否存在大量小分片,合并小索引,减少集群元数据内存占用;
- 调大 Coordinating 节点内存,避免大聚合查询导致协调节点 OOM。
9.3 磁盘空间不足
核心风险 :磁盘使用率超过 95% 后,ES 会自动将索引设置为只读,无法写入数据,业务受影响。解决方案:
json
# 1. 临时方案:清理过期无用索引,释放磁盘空间
DELETE /logs-2023-*
# 2. 长效方案:配置ILM索引生命周期管理,自动滚动与删除过期索引
PUT /_ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50GB",
"max_age": "7d"
}
}
},
"delete": {
"min_age": "30d",
"actions": {
"delete": {}
}
}
}
}
}
# 3. 扩容方案:增加 Data 节点,或扩容节点磁盘空间
十、总结
Elasticsearch 生产环境的稳定运行,是一套全流程的体系化工作,核心最佳实践可总结为 8 大核心要点:
- 架构先行:根据业务规模合理拆分节点角色,匹配对应的硬件资源,避免混部导致的故障扩散;
- JVM 调优:严格遵守堆内存分配原则,不超过 32GB,预留足够内存给 Lucene 缓存,优化 GC 配置;
- 索引设计:合理规划分片数量,优化 Mapping 配置,通过索引模板保证规范统一;
- 读写优化:查询优先使用 Filter 缓存,避免深度分页;写入使用 Bulk 批量提交,合理调整刷新间隔;
- 监控告警:建立全维度的监控体系,覆盖集群健康、节点性能、慢查询,提前发现隐患;
- 数据备份:定期创建快照,实现自动备份与过期清理,保证数据可恢复;
- 安全加固:开启 X-Pack 安全认证,遵循最小权限原则分配账号,开启 SSL 加密;
- 故障预案:掌握常见故障的排查与解决方法,避免故障发生后手忙脚乱。
本文所有配置与代码均经过生产环境验证,可直接落地复用,希望能帮助大家打造高可用、高性能的 Elasticsearch 集群。