ELK 从入门到精通:Spring Boot 实战三部曲(二)------ 进阶特性与性能优化
专题导读:本系列共三篇,带你系统掌握 ELK 在 Spring Boot 项目中的实战应用。
- 第一篇 基础核心与快速上手
- 第二篇:进阶特性与性能优化(本文)
- 第三篇 高级应用与架构设计
📖 前言
在上一篇文章中,我们学习了 ELK 的基础概念和基本操作。本文将深入探讨 Elasticsearch 的高级查询、性能优化技巧、Logstash 高级处理以及 Kibana 高级可视化,帮助你构建高性能的日志分析系统。
学完本文你将掌握:
- ✅ Elasticsearch 高级查询与聚合分析
- ✅ 性能优化技巧与参数调优
- ✅ Logstash 高级过滤器与数据处理
- ✅ Kibana 高级可视化与告警配置
- ✅ 安全加固与权限控制
- ✅ 数据备份与恢复策略
一、Elasticsearch 高级查询
1.1 全文检索优化
Multi-Match 查询
java
/**
* 多字段搜索
*/
public List<ProductDocument> searchProducts(String keyword) {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword)
.field("name", 3.0f) // name 字段权重更高
.field("description", 1.0f)
.field("category", 1.5f)
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
)
.withPageable(PageRequest.of(0, 20))
.build();
SearchHits<ProductDocument> hits = elasticsearchTemplate.search(
query, ProductDocument.class
);
return hits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
高亮显示
java
/**
* 带高亮的搜索
*/
public Map<String, Object> searchWithHighlight(String keyword) {
HighlightBuilder highlightBuilder = new HighlightBuilder()
.field(new HighlightBuilder.Field("name")
.preTags("<em class=\"highlight\">")
.postTags("</em>"))
.field(new HighlightBuilder.Field("description")
.fragmentSize(150)
.numOfFragments(3));
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withHighlightBuilder(highlightBuilder)
.withPageable(PageRequest.of(0, 10))
.build();
SearchHits<ProductDocument> hits = elasticsearchTemplate.search(
query, ProductDocument.class
);
List<Map<String, Object>> results = new ArrayList<>();
for (SearchHit<ProductDocument> hit : hits) {
Map<String, Object> result = new HashMap<>();
result.put("document", hit.getContent());
result.put("highlights", hit.getHighlightFields());
results.add(result);
}
Map<String, Object> response = new HashMap<>();
response.put("total", hits.getTotalHits());
response.put("results", results);
return response;
}
1.2 复杂聚合分析
嵌套聚合
java
/**
* 多维度统计分析
*/
public Map<String, Object> complexAggregation() {
// 按类别分组,再按价格区间统计
TermsAggregationBuilder categoryAgg = AggregationBuilders
.terms("by_category")
.field("category.keyword")
.size(10)
.subAggregation(
AggregationBuilders.range("price_range")
.field("price")
.addUnboundedTo(100)
.addRange(100, 500)
.addRange(500, 1000)
.addUnboundedFrom(1000)
)
.subAggregation(
AggregationBuilders.avg("avg_price")
.field("price")
);
NativeSearchQuery query = new NativeSearchQueryBuilder()
.addAggregation(categoryAgg)
.build();
SearchHits<ProductDocument> hits = elasticsearchTemplate.search(
query, ProductDocument.class
);
Terms categories = hits.getAggregations().get("by_category");
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> categoryStats = new ArrayList<>();
for (Terms.Bucket bucket : categories.getBuckets()) {
Map<String, Object> categoryData = new HashMap<>();
categoryData.put("category", bucket.getKeyAsString());
categoryData.put("count", bucket.getDocCount());
// 价格区间分布
Range priceRange = bucket.getAggregations().get("price_range");
List<Map<String, Object>> ranges = new ArrayList<>();
for (Range.Bucket rangeBucket : priceRange.getBuckets()) {
Map<String, Object> rangeData = new HashMap<>();
rangeData.put("range", rangeBucket.getKeyAsString());
rangeData.put("count", rangeBucket.getDocCount());
ranges.add(rangeData);
}
categoryData.put("priceDistribution", ranges);
// 平均价格
Avg avgPrice = bucket.getAggregations().get("avg_price");
categoryData.put("avgPrice", avgPrice.getValue());
categoryStats.add(categoryData);
}
result.put("categories", categoryStats);
return result;
}
日期直方图聚合
java
/**
* 时间序列分析
*/
public List<Map<String, Object>> timeSeriesAnalysis() {
DateHistogramAggregationBuilder dateHistogram = AggregationBuilders
.dateHistogram("sales_over_time")
.field("createTime")
.calendarInterval(DateHistogramInterval.DAY)
.format("yyyy-MM-dd")
.subAggregation(
AggregationBuilders.sum("daily_sales")
.field("price")
)
.subAggregation(
AggregationBuilders.cardinality("unique_customers")
.field("customerId.keyword")
);
NativeSearchQuery query = new NativeSearchQueryBuilder()
.addAggregation(dateHistogram)
.build();
SearchHits<OrderDocument> hits = elasticsearchTemplate.search(
query, OrderDocument.class
);
Histogram histogram = hits.getAggregations().get("sales_over_time");
List<Map<String, Object>> result = new ArrayList<>();
for (Histogram.Bucket bucket : histogram.getBuckets()) {
Map<String, Object> data = new HashMap<>();
data.put("date", bucket.getKeyAsString());
data.put("orderCount", bucket.getDocCount());
Sum dailySales = bucket.getAggregations().get("daily_sales");
data.put("totalSales", dailySales.getValue());
Cardinality uniqueCustomers = bucket.getAggregations().get("unique_customers");
data.put("uniqueCustomers", uniqueCustomers.getValue());
result.add(data);
}
return result;
}
1.3 地理位置查询
java
@Data
@Document(indexName = "store_index")
public class StoreDocument {
@Id
private String id;
@Field(type = FieldType.Text)
private String name;
@GeoPointField
private GeoPoint location;
@Field(type = FieldType.Keyword)
private String address;
}
/**
* 附近搜索
*/
public List<StoreDocument> searchNearby(double lat, double lon, double distanceKm) {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.geoDistanceQuery("location")
.point(lat, lon)
.distance(distanceKm, DistanceUnit.KILOMETERS)
)
.withSort(SortBuilders.geoDistanceSort("location", lat, lon)
.order(SortOrder.ASC)
.unit(DistanceUnit.KILOMETERS)
)
.withPageable(PageRequest.of(0, 20))
.build();
SearchHits<StoreDocument> hits = elasticsearchTemplate.search(
query, StoreDocument.class
);
return hits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
二、性能优化实战
2.1 索引优化
Mapping 优化
java
@Data
@Document(indexName = "optimized_index")
@Setting(settingPath = "/elasticsearch/settings.json")
public class OptimizedDocument {
@Id
private String id;
// 不需要分词的字段使用 keyword
@Field(type = FieldType.Keyword)
private String status;
// 需要全文检索的字段使用 text + ik 分词
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String content;
// 数值类型选择合适的精度
@Field(type = FieldType.Integer)
private Integer count;
@Field(type = FieldType.Long)
private Long timestamp;
// 不需要存储的字段
@Field(type = FieldType.Text, store = false)
private String description;
// 禁用 _all 字段(7.x已移除)
// 禁用 source(谨慎使用)
}
settings.json:
json
{
"index": {
"number_of_shards": 3,
"number_of_replicas": 1,
"refresh_interval": "30s",
"translog": {
"durability": "async",
"sync_interval": "5s"
},
"merge": {
"policy": {
"max_merged_segment": "5gb",
"segments_per_tier": 10
}
}
}
}
批量导入优化
java
@Service
public class BulkImportService {
@Autowired
private ElasticsearchRestTemplate elasticsearchTemplate;
private static final int BATCH_SIZE = 1000;
/**
* 批量导入数据
*/
public void bulkImport(List<ProductDocument> products) {
List<IndexQuery> queries = new ArrayList<>();
for (ProductDocument product : products) {
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(product.getId())
.withObject(product)
.build();
queries.add(indexQuery);
// 分批提交
if (queries.size() >= BATCH_SIZE) {
elasticsearchTemplate.bulkIndex(queries, IndexCoordinates.of("product_index"));
queries.clear();
log.info("已导入 {} 条数据", products.size());
}
}
// 提交剩余数据
if (!queries.isEmpty()) {
elasticsearchTemplate.bulkIndex(queries, IndexCoordinates.of("product_index"));
}
// 强制刷新
elasticsearchTemplate.indexOps(IndexCoordinates.of("product_index")).refresh();
}
}
2.2 查询优化
使用 Filter 缓存
java
/**
* 优化查询:将不变的条件放在 filter 中
*/
public List<ProductDocument> optimizedSearch(String keyword, String category) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("name", keyword)) // 会计算评分
.filter(QueryBuilders.termQuery("category", category)) // 使用缓存,不计算评分
.filter(QueryBuilders.termQuery("status", "ACTIVE"));
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withPageable(PageRequest.of(0, 20))
.build();
SearchHits<ProductDocument> hits = elasticsearchTemplate.search(
query, ProductDocument.class
);
return hits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
分页优化:Search After
java
/**
* 深分页优化:使用 search_after
*/
public List<ProductDocument> deepPagination(String keyword, int pageSize, String sortValue) {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withSort(SortBuilders.fieldSort("id").order(SortOrder.ASC))
.withPageable(PageRequest.of(0, pageSize))
.build();
// 如果有上一页的最后一条记录的排序值
if (sortValue != null) {
query.setSearchAfter(new Object[]{sortValue});
}
SearchHits<ProductDocument> hits = elasticsearchTemplate.search(
query, ProductDocument.class
);
return hits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
路由优化
java
/**
* 使用路由提高查询效率
*/
public void saveWithRouting(ProductDocument product) {
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(product.getId())
.withObject(product)
.withRouting(product.getCategoryId()) // 指定路由
.build();
elasticsearchTemplate.index(indexQuery, IndexCoordinates.of("product_index"));
}
public List<ProductDocument> searchWithRouting(String keyword, String categoryId) {
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withRoute(categoryId) // 只查询特定分片
.build();
SearchHits<ProductDocument> hits = elasticsearchTemplate.search(
query, ProductDocument.class
);
return hits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
2.3 JVM 与系统优化
JVM 参数调优
conf
# jvm.options
-Xms4g
-Xmx4g
# GC 配置
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
# 禁止 Swap
-XX:+AlwaysPreTouch
系统配置
bash
# /etc/sysctl.conf
vm.max_map_count=262144
# /etc/security/limits.conf
elasticsearch soft nofile 65536
elasticsearch hard nofile 65536
elasticsearch soft nproc 4096
elasticsearch hard nproc 4096
三、Logstash 高级处理
3.1 多管道配置
logstash/pipeline/multi-pipeline.yml:
yaml
- pipeline.id: app-logs
path.config: "/usr/share/logstash/pipeline/app-logs.conf"
pipeline.workers: 4
- pipeline.id: error-logs
path.config: "/usr/share/logstash/pipeline/error-logs.conf"
pipeline.workers: 2
- pipeline.id: metrics
path.config: "/usr/share/logstash/pipeline/metrics.conf"
pipeline.workers: 2
3.2 高级过滤器
Grok 解析日志
conf
filter {
# 解析 Nginx 访问日志
grok {
match => {
"message" => "%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response} %{NUMBER:bytes}"
}
}
# 解析日期
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
# IP 地理位置
geoip {
source => "clientip"
target => "geoip"
}
# 用户代理解析
useragent {
source => "agent"
target => "useragent"
}
}
条件判断
conf
filter {
# 根据日志级别分流
if [level] == "ERROR" {
mutate {
add_tag => ["error"]
add_field => { "alert_level" => "high" }
}
} else if [level] == "WARN" {
mutate {
add_tag => ["warning"]
add_field => { "alert_level" => "medium" }
}
}
# 删除调试日志
if [level] == "DEBUG" and [environment] == "production" {
drop {}
}
}
Ruby 脚本处理
conf
filter {
ruby {
code => "
# 计算响应时间等级
response_time = event.get('response_time').to_f
if response_time < 100
event.set('performance_level', 'fast')
elsif response_time < 500
event.set('performance_level', 'normal')
else
event.set('performance_level', 'slow')
end
# 添加小时字段用于聚合
timestamp = event.get('@timestamp')
hour = timestamp.strftime('%H')
event.set('hour_of_day', hour)
"
}
}
3.3 输出优化
conf
output {
# 根据日志级别输出到不同索引
if "error" in [tags] {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "error-logs-%{+YYYY.MM.dd}"
template_overwrite => true
}
} else {
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}
# 错误日志发送到告警系统
if [alert_level] == "high" {
http {
url => "http://alert-system/api/alerts"
http_method => "post"
format => "json"
}
}
}
四、Kibana 高级可视化
4.1 Lens 可视化
创建交互式仪表板:
- 进入 Visualize Library → Create visualization → Lens
- 拖拽字段创建图表
- 添加多个图层
- 配置交互:点击下钻、联动过滤
4.2 TSVB 时间序列
监控系统性能指标:
- 选择 Time Series Visual Builder
- 配置面板:
- Panel 1: CPU 使用率(折线图)
- Panel 2: 内存使用率(面积图)
- Panel 3: 磁盘 IO(柱状图)
- 设置阈值线和告警
4.3 Canvas 实时大屏
创建实时监控大屏:
- 进入 Canvas → Create workpad
- 添加元素:
- 实时数据卡片
- 动态图表
- 地图展示
- 滚动列表
- 设置自动刷新(5秒)
示例表达式:
filters
| essql query="SELECT COUNT(*) as count FROM \"app-logs-*\" WHERE level='ERROR' AND @timestamp > NOW() - 1 HOUR"
| metric "当前错误数" font={font family="'Open Sans', Helvetica, Arial, sans-serif" size=48 align="center"}
| render
4.4 告警配置
Watcher 告警
json
PUT _watcher/watch/error_rate_alert
{
"trigger": {
"schedule": {
"interval": "5m"
}
},
"input": {
"search": {
"request": {
"indices": ["app-logs-*"],
"body": {
"query": {
"bool": {
"must": [
{ "term": { "level": "ERROR" } },
{ "range": { "@timestamp": { "gte": "now-5m" } } }
]
}
},
"aggs": {
"error_count": {
"value_count": { "field": "_id" }
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.aggregations.error_count.value": {
"gt": 100
}
}
},
"actions": {
"send_email": {
"email": {
"to": "admin@example.com",
"subject": "错误率告警",
"body": "过去5分钟错误数量超过阈值: {{ctx.payload.aggregations.error_count.value}}"
}
},
"webhook": {
"webhook": {
"method": "POST",
"url": "http://dingtalk-webhook",
"body": "{\"msgtype\": \"text\", \"text\": {\"content\": \"错误率告警!\"}}"
}
}
}
}
五、安全加固
5.1 启用 X-Pack Security
docker-compose.yml:
yaml
services:
elasticsearch:
environment:
- xpack.security.enabled=true
- xpack.security.enrollment.enabled=true
- ELASTIC_PASSWORD=your_password
kibana:
environment:
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=your_password
生成密码:
bash
docker exec -it elasticsearch bin/elasticsearch-setup-passwords auto
5.2 角色与权限
json
// 创建只读角色
POST /_security/role/log_reader
{
"cluster": ["monitor"],
"indices": [
{
"names": ["app-logs-*"],
"privileges": ["read", "view_index_metadata"]
}
]
}
// 创建用户并分配角色
POST /_security/user/log_user
{
"password": "user_password",
"roles": ["log_reader"],
"full_name": "Log Reader"
}
5.3 SSL/TLS 加密
yaml
# elasticsearch.yml
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
xpack.security.transport.ssl:
enabled: true
verification_mode: certificate
keystore.path: certs/transport.p12
truststore.path: certs/transport.p12
六、数据备份与恢复
6.1 快照仓库
bash
# 注册快照仓库
PUT _snapshot/my_backup
{
"type": "fs",
"settings": {
"location": "/mnt/backups"
}
}
# 创建快照
PUT _snapshot/my_backup/snapshot_2024_01_01?wait_for_completion=true
{
"indices": "app-logs-*",
"ignore_unavailable": true,
"include_global_state": false
}
# 查看快照
GET _snapshot/my_backup/_all
# 恢复快照
POST _snapshot/my_backup/snapshot_2024_01_01/_restore
{
"indices": "app-logs-*",
"rename_pattern": "(.+)",
"rename_replacement": "restored_$1"
}
6.2 自动化备份脚本
bash
#!/bin/bash
BACKUP_DIR="/mnt/backups"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30
# 创建快照
curl -X PUT "localhost:9200/_snapshot/my_backup/snapshot_${DATE}?wait_for_completion=true" \
-H 'Content-Type: application/json' \
-d '{
"indices": "app-logs-*",
"ignore_unavailable": true,
"include_global_state": false
}'
# 删除过期快照
OLD_SNAPSHOTS=$(curl -s "localhost:9200/_snapshot/my_backup/_all" | jq -r ".snapshots[] | select(.start_timeInMillis < $(($(date +%s%N)/1000000 - ${RETENTION_DAYS}*86400*1000))) | .snapshot")
for snapshot in $OLD_SNAPSHOTS; do
curl -X DELETE "localhost:9200/_snapshot/my_backup/$snapshot"
echo "Deleted old snapshot: $snapshot"
done
七、监控与运维
7.1 Elastic Stack Monitoring
启用监控:
yaml
# elasticsearch.yml
xpack.monitoring.enabled: true
xpack.monitoring.collection.enabled: true
# kibana.yml
monitoring.ui.enabled: true
monitoring.kibana.collection.enabled: true
访问监控页面:
Kibana → Stack Management → Monitoring
7.2 关键指标监控
bash
# 集群健康
GET /_cluster/health
# 节点状态
GET /_nodes/stats
# 索引状态
GET /_cat/indices?v
# 慢查询日志
PUT /_cluster/settings
{
"persistent": {
"index.search.slowlog.threshold.query.warn": "10s",
"index.search.slowlog.threshold.fetch.debug": "500ms"
}
}
7.3 Prometheus 集成
安装 elasticsearch_exporter:
bash
docker run -d --name es-exporter \
-p 9114:9114 \
justwatch/elasticsearch_exporter \
--es.uri=http://elasticsearch:9200
Prometheus 配置:
yaml
scrape_configs:
- job_name: 'elasticsearch'
static_configs:
- targets: ['localhost:9114']
八、总结与展望
8.1 本文要点回顾
✅ 高级查询 :全文检索、聚合分析、地理位置
✅ 性能优化 :索引优化、查询优化、JVM 调优
✅ Logstash 高级 :多管道、Grok、条件判断
✅ Kibana 高级 :Lens、TSVB、Canvas、告警
✅ 安全加固 :X-Pack Security、SSL/TLS
✅ 备份恢复:快照管理、自动化脚本
8.2 下篇预告
在下一篇文章《ELK 从入门到精通:Spring Boot 实战三部曲(三)------ 高级应用与架构设计》中,我们将探讨:
- 🏗️ 大规模 ELK 集群架构设计
- 🔧 Filebeat 轻量级数据采集
- 🌐 APM 应用性能监控
- 📊 业务数据分析实战
- 🚀 云原生 ELK 部署
- 💡 最佳实践与案例分享
8.3 学习建议
- 持续优化:定期审查性能和资源使用
- 安全第一:生产环境必须启用安全认证
- 备份策略:制定完善的备份和恢复计划
- 监控告警:及时发现问题,避免故障扩大
📚 参考资料
- Elasticsearch 性能调优:https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html
- Logstash 最佳实践:https://www.elastic.co/guide/en/logstash/current/best-practices.html
- Kibana 可视化指南:https://www.elastic.co/guide/en/kibana/current/visualizations.html
觉得有用?欢迎点赞、收藏、转发!
下一篇更精彩,敬请期待! 🚀
系列文章:
- 第一篇 ELK 从入门到精通:Spring Boot 实战三部曲(一)------ 基础核心与快速上手
- 第二篇 ELK 从入门到精通:Spring Boot 实战三部曲(二)------ 进阶特性与性能优化
- 第三篇 ELK 从入门到精通:Spring Boot 实战三部曲(三)------ 高级应用与架构设计