【大数据】Elasticsearch 8.x 全面实战指南:系列教程导航

系列介绍

Elasticsearch作为当今最流行的分布式搜索和分析引擎,在大数据处理、日志分析、实时搜索等领域发挥着重要作用。本系列教程将带你从零开始,深入学习Elasticsearch 8.x的核心技术,掌握从基础操作到企业级应用的完整技能体系。

为了更好地满足不同水平开发者的学习需求,我们将教程按难度级别分为三个层次:入门级进阶级高级。每个级别都有明确的学习目标和实践重点,帮助你循序渐进地掌握Elasticsearch技术。

学习路径

📚 入门级:基础概念与环境搭建

适合人群:Elasticsearch初学者、刚接触搜索引擎技术的开发者

学习目标

  • 理解Elasticsearch核心概念和架构
  • 掌握环境搭建和基础配置
  • 学会基本的索引和文档操作
  • 掌握简单的搜索查询

文章链接【大数据】Elasticsearch 8.x 入门指南:基础概念与环境搭建

主要内容

  • Elasticsearch核心概念(集群、节点、索引、文档、分片)
  • Docker环境搭建与配置
  • Spring Boot 3集成配置
  • 基础索引操作(创建、删除、映射定义)
  • 基础文档操作(增删改查)
  • 简单搜索查询(全文搜索、精确匹配、范围查询)

🚀 进阶级:复杂查询与聚合分析

适合人群:有一定Elasticsearch基础,希望深入学习查询和分析功能的开发者

学习目标

  • 掌握复杂的查询语法和组合查询
  • 学会使用聚合功能进行数据分析
  • 了解性能优化的基础方法
  • 通过实战案例加深理解

文章链接【大数据】Elasticsearch 8.x 进阶指南:复杂查询与聚合分析

主要内容

  • 复杂查询语法(布尔查询、嵌套查询、脚本查询)
  • 聚合分析(桶聚合、指标聚合、管道聚合)
  • 搜索建议和自动补全
  • 高亮显示和搜索结果优化
  • 性能优化基础(索引优化、查询优化)
  • 电商搜索系统实战案例

⚡ 高级:集群管理与企业级应用

适合人群:需要在生产环境中部署和维护Elasticsearch集群的架构师和运维工程师

学习目标

  • 掌握集群架构设计和管理
  • 学会企业级安全配置
  • 掌握深度性能调优技术
  • 建立完整的监控和运维体系

文章链接【大数据】Elasticsearch 8.x 高级指南:集群管理与企业级应用

主要内容

  • 集群架构设计(主节点、数据节点、协调节点)
  • 企业级安全配置(SSL/TLS、RBAC、审计)
  • 深度性能调优(JVM调优、索引调优、查询调优)
  • 监控与运维(健康检查、性能监控、日志分析)
  • 备份与恢复策略
  • 企业级最佳实践

学习建议

🎯 学习顺序

  1. 从入门级开始:即使有一定基础,也建议先阅读入门级教程,确保概念理解准确
  2. 循序渐进:按照入门→进阶→高级的顺序学习,每个级别都有前一级别的知识基础
  3. 实践为主:每个教程都提供了完整的代码示例,建议边学边练
  4. 项目实战:学完每个级别后,尝试在实际项目中应用所学知识

💡 学习技巧

  • 环境准备:建议使用Docker搭建学习环境,方便快速部署和重置
  • 代码实践:所有示例代码都经过测试,可以直接运行
  • 文档查阅:结合官方文档学习,加深对API和配置的理解
  • 社区交流:遇到问题时,可以在Elasticsearch社区寻求帮助

📖 扩展学习

完成本系列教程后,建议继续学习:

  1. ELK Stack:学习Logstash和Kibana,构建完整的日志分析解决方案
  2. Beats系列:掌握各种数据采集工具
  3. 机器学习:了解Elasticsearch的ML功能
  4. 云服务:学习Elastic Cloud等云服务

技术栈说明

本系列教程使用的主要技术栈:

  • Elasticsearch: 8.x 最新版本
  • Java: 17+ (Spring Boot 3要求)
  • Spring Boot: 3.x
  • Docker: 用于环境搭建
  • Maven: 项目构建工具

总结

Elasticsearch是一个功能强大且复杂的系统,掌握它需要循序渐进的学习过程。通过本系列教程的学习,你将:

  • 🎯 建立完整的知识体系:从基础概念到企业级应用
  • 🚀 获得实战经验:通过丰富的代码示例和实战案例
  • 掌握最佳实践:学习企业级部署和运维经验
  • 📈 提升技术能力:成为Elasticsearch技术专家

选择适合你当前水平的教程开始学习吧!如果你是初学者,建议从入门级教程开始;如果已有基础,可以直接跳转到相应级别的教程。


系列文章

一、核心概念与架构

(一)基础概念

1. 集群(Cluster)

集群是一个或多个节点的集合,它们共同保存整个数据,并提供跨所有节点的联合索引和搜索功能。每个集群都有一个唯一的名称标识。

2. 节点(Node)

节点是集群中的单个服务器,存储数据并参与集群的索引和搜索功能。每个节点都有一个唯一的名称,默认在启动时分配一个随机的UUID。

3. 索引(Index)

索引是具有相似特征的文档集合。在Elasticsearch中,索引类似于关系数据库中的数据库概念。

4. 文档(Document)

文档是可以被索引的基本信息单元,以JSON格式表示。在索引中,每个文档都有一个唯一的ID。

5. 分片(Shard)

索引可以被分割成多个分片,每个分片本身就是一个功能完整且独立的”索引”。分片分为主分片(Primary Shard)和副本分片(Replica Shard)。

(二)应用场景

  • 全文搜索:网站搜索、企业内部搜索
  • 日志分析:ELK Stack中的核心组件
  • 实时数据分析:业务指标监控、用户行为分析
  • 地理位置搜索:基于位置的服务应用
  • 推荐系统:基于用户行为的个性化推荐

(三)Elasticsearch 8.x 新特性

1. 新的Java客户端

Elasticsearch 8.x引入了全新的Java客户端,提供了更好的类型安全和API设计:

1
2
3
4
5
6
7
8
9
10
11
12
// 新的Java客户端示例
ElasticsearchClient client = new ElasticsearchClient(transport);

// 类型安全的搜索请求
SearchResponse<Product> response = client.search(s -> s
.index("products")
.query(q -> q
.match(t -> t
.field("name")
.query("elasticsearch")
)
), Product.class);

2. 增强的安全性

  • 默认启用安全功能
  • 改进的SSL/TLS配置
  • 增强的角色和权限管理

3. 向量搜索支持

支持密集向量字段类型,为机器学习和AI应用提供原生支持:

1
2
3
4
5
6
7
8
9
10
{
"mappings": {
"properties": {
"text_vector": {
"type": "dense_vector",
"dims": 768
}
}
}
}

4. 性能改进

  • 更快的索引速度
  • 优化的查询性能
  • 改进的内存使用

二、环境搭建与配置

(一)安装Elasticsearch

1. Docker安装(推荐)

1
2
3
4
5
6
7
8
9
10
11
# 拉取Elasticsearch 8.x镜像
docker pull docker.elastic.co/elasticsearch/elasticsearch:8.12.0

# 运行单节点集群
docker run -d \
--name elasticsearch \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
docker.elastic.co/elasticsearch/elasticsearch:8.12.0

2. 基础配置

1
2
3
4
5
6
7
8
9
10
11
# elasticsearch.yml 核心配置
cluster.name: my-elasticsearch-cluster
node.name: node-1
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 0.0.0.0
http.port: 9200
discovery.type: single-node

# 内存设置
bootstrap.memory_lock: true

(二)Spring Boot 3 集成

1. 依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot 3 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.0</version>
</dependency>

<!-- Elasticsearch Java API Client -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.12.0</version>
</dependency>

<!-- Jackson JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>

2. 客户端配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Configuration
public class ElasticsearchConfig {

@Value("${elasticsearch.host:localhost}")
private String host;

@Value("${elasticsearch.port:9200}")
private int port;

/**
* 配置Elasticsearch客户端
* 使用新的Java API Client,替代已废弃的RestHighLevelClient
*/
@Bean
public ElasticsearchClient elasticsearchClient() {
// 创建低级别REST客户端
RestClient restClient = RestClient.builder(
new HttpHost(host, port, "http")
).build();

// 创建传输层,使用Jackson进行JSON序列化
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);

// 创建高级别客户端
return new ElasticsearchClient(transport);
}
}

三、索引与文档操作

(一)索引管理

1. 创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Service
public class IndexService {

@Autowired
private ElasticsearchClient client;

/**
* 创建索引,包含映射定义
*/
public void createIndex(String indexName) throws IOException {
// 定义索引映射
TypeMapping mapping = TypeMapping.of(m -> m
.properties("id", Property.of(p -> p.keyword(k -> k)))
.properties("title", Property.of(p -> p.text(t -> t
.analyzer("standard") // 使用标准分析器
.searchAnalyzer("standard")
)))
.properties("content", Property.of(p -> p.text(t -> t
.analyzer("ik_max_word") // 使用IK分词器(需要安装插件)
)))
.properties("createTime", Property.of(p -> p.date(d -> d
.format("yyyy-MM-dd HH:mm:ss")
)))
.properties("tags", Property.of(p -> p.keyword(k -> k)))
);

// 创建索引请求
CreateIndexRequest request = CreateIndexRequest.of(i -> i
.index(indexName)
.mappings(mapping)
.settings(s -> s
.numberOfShards("3") // 主分片数量
.numberOfReplicas("1") // 副本数量
.refreshInterval(t -> t.time("1s")) // 刷新间隔
)
);

// 执行创建
CreateIndexResponse response = client.indices().create(request);
System.out.println("索引创建结果: " + response.acknowledged());
}

/**
* 检查索引是否存在
*/
public boolean indexExists(String indexName) throws IOException {
ExistsRequest request = ExistsRequest.of(e -> e.index(indexName));
return client.indices().exists(request).value();
}
}

(二)文档操作

1. 文档实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 文档实体类
* 使用Jackson注解进行JSON序列化配置
*/
public class Document {
private String id;
private String title;
private String content;

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;

private List<String> tags;

// 构造函数
public Document() {}

public Document(String id, String title, String content) {
this.id = id;
this.title = title;
this.content = content;
this.createTime = LocalDateTime.now();
this.tags = new ArrayList<>();
}

// Getter和Setter方法
public String getId() { return id; }
public void setId(String id) { this.id = id; }

public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }

public String getContent() { return content; }
public void setContent(String content) { this.content = content; }

public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }

public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
}

2. 文档CRUD操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@Service
public class DocumentService {

@Autowired
private ElasticsearchClient client;

/**
* 添加文档
*/
public void addDocument(String indexName, Document document) throws IOException {
IndexRequest<Document> request = IndexRequest.of(i -> i
.index(indexName)
.id(document.getId())
.document(document)
);

IndexResponse response = client.index(request);
System.out.println("文档添加结果: " + response.result());
}

/**
* 批量添加文档
*/
public void bulkAddDocuments(String indexName, List<Document> documents) throws IOException {
BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();

// 构建批量请求
for (Document doc : documents) {
bulkBuilder.operations(op -> op
.index(idx -> idx
.index(indexName)
.id(doc.getId())
.document(doc)
)
);
}

// 执行批量操作
BulkResponse response = client.bulk(bulkBuilder.build());

if (response.errors()) {
System.err.println("批量操作存在错误");
for (BulkResponseItem item : response.items()) {
if (item.error() != null) {
System.err.println("错误: " + item.error().reason());
}
}
} else {
System.out.println("批量添加成功,处理了 " + response.items().size() + " 个文档");
}
}

/**
* 获取文档
*/
public Document getDocument(String indexName, String id) throws IOException {
GetRequest request = GetRequest.of(g -> g
.index(indexName)
.id(id)
);

GetResponse<Document> response = client.get(request, Document.class);

if (response.found()) {
return response.source();
}
return null;
}

/**
* 更新文档
*/
public void updateDocument(String indexName, String id, Document document) throws IOException {
UpdateRequest<Document, Document> request = UpdateRequest.of(u -> u
.index(indexName)
.id(id)
.doc(document)
);

UpdateResponse<Document> response = client.update(request, Document.class);
System.out.println("文档更新结果: " + response.result());
}

/**
* 删除文档
*/
public void deleteDocument(String indexName, String id) throws IOException {
DeleteRequest request = DeleteRequest.of(d -> d
.index(indexName)
.id(id)
);

DeleteResponse response = client.delete(request);
System.out.println("文档删除结果: " + response.result());
}
}

四、搜索与查询

(一)基础查询

1. 搜索服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@Service
public class SearchService {

@Autowired
private ElasticsearchClient client;

/**
* 全文搜索
*/
public List<Document> searchDocuments(String indexName, String keyword) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index(indexName)
.query(q -> q
.multiMatch(m -> m
.query(keyword)
.fields("title^2", "content") // title字段权重为2
.type(TextQueryType.BestFields)
)
)
.highlight(h -> h
.fields("title", hf -> hf)
.fields("content", hf -> hf)
)
.size(20) // 返回前20条结果
);

SearchResponse<Document> response = client.search(request, Document.class);

List<Document> results = new ArrayList<>();
for (Hit<Document> hit : response.hits().hits()) {
Document doc = hit.source();
// 处理高亮结果
if (hit.highlight() != null) {
System.out.println("高亮片段: " + hit.highlight());
}
results.add(doc);
}

return results;
}

/**
* 精确匹配查询
*/
public List<Document> searchByTag(String indexName, String tag) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index(indexName)
.query(q -> q
.term(t -> t
.field("tags")
.value(tag)
)
)
);

SearchResponse<Document> response = client.search(request, Document.class);
return response.hits().hits().stream()
.map(Hit::source)
.collect(Collectors.toList());
}

/**
* 范围查询
*/
public List<Document> searchByDateRange(String indexName, LocalDateTime startTime, LocalDateTime endTime) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index(indexName)
.query(q -> q
.range(r -> r
.field("createTime")
.gte(JsonData.of(startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))))
.lte(JsonData.of(endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))))
)
)
.sort(so -> so
.field(f -> f
.field("createTime")
.order(SortOrder.Desc)
)
)
);

SearchResponse<Document> response = client.search(request, Document.class);
return response.hits().hits().stream()
.map(Hit::source)
.collect(Collectors.toList());
}
}

(二)聚合查询

1. 聚合分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Service
public class AggregationService {

@Autowired
private ElasticsearchClient client;

/**
* 标签聚合统计
*/
public Map<String, Long> getTagsAggregation(String indexName) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index(indexName)
.size(0) // 不返回文档,只返回聚合结果
.aggregations("tags_agg", a -> a
.terms(t -> t
.field("tags")
.size(10) // 返回前10个标签
)
)
);

SearchResponse<Void> response = client.search(request, Void.class);

Map<String, Long> tagCounts = new HashMap<>();

// 处理聚合结果
StringTermsAggregate tagsAgg = response.aggregations()
.get("tags_agg")
.sterms();

for (StringTermsBucket bucket : tagsAgg.buckets().array()) {
tagCounts.put(bucket.key().stringValue(), bucket.docCount());
}

return tagCounts;
}

/**
* 日期直方图聚合
*/
public Map<String, Long> getDateHistogram(String indexName) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index(indexName)
.size(0)
.aggregations("date_histogram", a -> a
.dateHistogram(d -> d
.field("createTime")
.calendarInterval(CalendarInterval.Day) // 按天聚合
.format("yyyy-MM-dd")
)
)
);

SearchResponse<Void> response = client.search(request, Void.class);

Map<String, Long> dateCount = new LinkedHashMap<>();

DateHistogramAggregate dateAgg = response.aggregations()
.get("date_histogram")
.dateHistogram();

for (DateHistogramBucket bucket : dateAgg.buckets().array()) {
dateCount.put(bucket.keyAsString(), bucket.docCount());
}

return dateCount;
}
}

五、性能优化与最佳实践

(一)索引优化

1. 索引设置优化

1
2
3
4
5
6
7
8
9
10
11
12
# elasticsearch.yml 性能优化配置
# 内存设置
indices.memory.index_buffer_size: 20%
indices.memory.min_index_buffer_size: 96mb

# 刷新设置
indices.memory.interval: 5s
index.refresh_interval: 30s

# 合并设置
index.merge.policy.max_merge_at_once: 5
index.merge.policy.segments_per_tier: 5

2. 批量操作优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Service
public class OptimizedBulkService {

@Autowired
private ElasticsearchClient client;

/**
* 优化的批量插入
* 使用合适的批次大小和并发控制
*/
public void optimizedBulkInsert(String indexName, List<Document> documents) throws IOException {
final int BATCH_SIZE = 1000; // 每批1000个文档
final int THREAD_COUNT = 4; // 4个并发线程

ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
List<Future<Void>> futures = new ArrayList<>();

// 分批处理
for (int i = 0; i < documents.size(); i += BATCH_SIZE) {
int endIndex = Math.min(i + BATCH_SIZE, documents.size());
List<Document> batch = documents.subList(i, endIndex);

Future<Void> future = executor.submit(() -> {
try {
processBatch(indexName, batch);
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
});

futures.add(future);
}

// 等待所有批次完成
for (Future<Void> future : futures) {
try {
future.get();
} catch (Exception e) {
System.err.println("批量处理出错: " + e.getMessage());
}
}

executor.shutdown();
}

private void processBatch(String indexName, List<Document> batch) throws IOException {
BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();

for (Document doc : batch) {
bulkBuilder.operations(op -> op
.index(idx -> idx
.index(indexName)
.id(doc.getId())
.document(doc)
)
);
}

BulkResponse response = client.bulk(bulkBuilder.build());

if (response.errors()) {
System.err.println("批次处理存在错误");
} else {
System.out.println("成功处理批次,文档数量: " + batch.size());
}
}
}

(二)查询优化

1. 查询性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@Service
public class OptimizedSearchService {

@Autowired
private ElasticsearchClient client;

/**
* 优化的搜索查询
* 使用过滤器、缓存和合适的查询类型
*/
public SearchResult optimizedSearch(SearchParams params) throws IOException {
SearchRequest request = SearchRequest.of(s -> s
.index(params.getIndexName())
.query(q -> q
.bool(b -> b
// 使用must查询进行评分
.must(must -> must
.multiMatch(m -> m
.query(params.getKeyword())
.fields("title^3", "content")
.type(TextQueryType.BestFields)
.minimumShouldMatch("75%") // 最小匹配度
)
)
// 使用filter进行精确过滤(不参与评分,可缓存)
.filter(filter -> filter
.terms(t -> t
.field("tags")
.terms(TermsQueryField.of(tf -> tf.value(
params.getTags().stream()
.map(FieldValue::of)
.collect(Collectors.toList())
)))
)
)
.filter(filter -> filter
.range(r -> r
.field("createTime")
.gte(JsonData.of(params.getStartTime()))
.lte(JsonData.of(params.getEndTime()))
)
)
)
)
.sort(so -> so
.score(sc -> sc.order(SortOrder.Desc)) // 按相关性排序
.field(f -> f.field("createTime").order(SortOrder.Desc)) // 再按时间排序
)
.from(params.getFrom())
.size(params.getSize())
.source(src -> src
.filter(f -> f
.includes("id", "title", "createTime", "tags") // 只返回需要的字段
.excludes("content") // 排除大字段
)
)
);

SearchResponse<Document> response = client.search(request, Document.class);

return new SearchResult(
response.hits().hits().stream().map(Hit::source).collect(Collectors.toList()),
response.hits().total().value(),
response.took()
);
}
}

// 搜索参数类
public class SearchParams {
private String indexName;
private String keyword;
private List<String> tags;
private String startTime;
private String endTime;
private int from = 0;
private int size = 20;

// Getter和Setter方法...
}

// 搜索结果类
public class SearchResult {
private List<Document> documents;
private long totalHits;
private long took;

public SearchResult(List<Document> documents, long totalHits, long took) {
this.documents = documents;
this.totalHits = totalHits;
this.took = took;
}

// Getter方法...
}

六、实战案例

(一)电商搜索系统

1. 商品搜索实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/**
* 电商商品搜索服务
* 实现商品的多维度搜索功能
*/
@Service
public class ProductSearchService {

@Autowired
private ElasticsearchClient client;

/**
* 商品综合搜索
* 支持关键词、价格范围、分类、品牌等多维度搜索
*/
public ProductSearchResult searchProducts(ProductSearchRequest request) throws IOException {
SearchRequest searchRequest = SearchRequest.of(s -> s
.index("products")
.query(q -> q
.bool(b -> {
// 关键词搜索
if (StringUtils.hasText(request.getKeyword())) {
b.must(must -> must
.multiMatch(m -> m
.query(request.getKeyword())
.fields("name^3", "description^2", "brand", "category")
.type(TextQueryType.BestFields)
.fuzziness("AUTO") // 自动模糊匹配
)
);
}

// 价格范围过滤
if (request.getMinPrice() != null || request.getMaxPrice() != null) {
b.filter(filter -> filter
.range(r -> {
r.field("price");
if (request.getMinPrice() != null) {
r.gte(JsonData.of(request.getMinPrice()));
}
if (request.getMaxPrice() != null) {
r.lte(JsonData.of(request.getMaxPrice()));
}
return r;
})
);
}

// 分类过滤
if (request.getCategory() != null) {
b.filter(filter -> filter
.term(t -> t.field("category").value(request.getCategory()))
);
}

// 品牌过滤
if (request.getBrands() != null && !request.getBrands().isEmpty()) {
b.filter(filter -> filter
.terms(t -> t
.field("brand")
.terms(TermsQueryField.of(tf -> tf.value(
request.getBrands().stream()
.map(FieldValue::of)
.collect(Collectors.toList())
)))
)
);
}

return b;
})
)
.sort(so -> {
// 排序逻辑
switch (request.getSortBy()) {
case "price_asc":
return so.field(f -> f.field("price").order(SortOrder.Asc));
case "price_desc":
return so.field(f -> f.field("price").order(SortOrder.Desc));
case "sales":
return so.field(f -> f.field("sales").order(SortOrder.Desc));
default:
return so.score(sc -> sc.order(SortOrder.Desc));
}
})
.from(request.getFrom())
.size(request.getSize())
// 聚合统计
.aggregations("categories", a -> a
.terms(t -> t.field("category").size(10))
)
.aggregations("brands", a -> a
.terms(t -> t.field("brand").size(20))
)
.aggregations("price_ranges", a -> a
.range(r -> r
.field("price")
.ranges(range -> range.to(JsonData.of(100)))
.ranges(range -> range.from(JsonData.of(100)).to(JsonData.of(500)))
.ranges(range -> range.from(JsonData.of(500)).to(JsonData.of(1000)))
.ranges(range -> range.from(JsonData.of(1000)))
)
)
);

SearchResponse<Product> response = client.search(searchRequest, Product.class);

// 构建搜索结果
return buildSearchResult(response);
}

private ProductSearchResult buildSearchResult(SearchResponse<Product> response) {
// 提取商品列表
List<Product> products = response.hits().hits().stream()
.map(Hit::source)
.collect(Collectors.toList());

// 提取聚合结果
Map<String, Long> categories = extractTermsAggregation(response, "categories");
Map<String, Long> brands = extractTermsAggregation(response, "brands");
Map<String, Long> priceRanges = extractRangeAggregation(response, "price_ranges");

return new ProductSearchResult(
products,
response.hits().total().value(),
categories,
brands,
priceRanges
);
}

private Map<String, Long> extractTermsAggregation(SearchResponse<Product> response, String aggName) {
return response.aggregations().get(aggName).sterms().buckets().array().stream()
.collect(Collectors.toMap(
bucket -> bucket.key().stringValue(),
StringTermsBucket::docCount,
(v1, v2) -> v1,
LinkedHashMap::new
));
}

private Map<String, Long> extractRangeAggregation(SearchResponse<Product> response, String aggName) {
return response.aggregations().get(aggName).range().buckets().array().stream()
.collect(Collectors.toMap(
bucket -> bucket.key(),
RangeBucket::docCount,
(v1, v2) -> v1,
LinkedHashMap::new
));
}
}

七、总结与最佳实践

(一)核心优势

Elasticsearch 8.x 在以下方面表现出色:

  1. 强大的搜索能力:支持全文搜索、结构化搜索、地理位置搜索等
  2. 高性能:分布式架构,支持水平扩展,处理PB级数据
  3. 实时性:近实时搜索和分析能力
  4. 易用性:RESTful API,丰富的客户端支持
  5. 生态完整:与ELK Stack无缝集成

(二)最佳实践总结

1. 索引设计原则

合理的索引设计要点:

  • 分片数量设置 - 通常每个分片控制在20-40GB之间
  • 映射类型选择 - 使用合适的映射类型和分析器
  • 结构设计 - 避免过深的嵌套结构,影响查询性能
  • 字段类型 - 合理使用keyword和text类型,根据查询需求选择

2. 性能优化要点

  • 批量操作:使用bulk API进行批量插入和更新
  • 查询优化:使用filter替代query进行精确匹配
  • 内存管理:合理设置JVM堆内存大小
  • 监控告警:建立完善的监控体系

3. 运维建议

  • 定期维护:定期清理过期数据,优化索引
  • 备份策略:建立完善的快照备份机制
  • 安全配置:启用安全功能,配置用户权限
  • 版本管理:及时更新到稳定版本

(三)学习建议

  1. 基础概念:深入理解Elasticsearch的核心概念和架构
  2. 实践项目:通过实际项目加深理解
  3. 性能调优:学习性能监控和调优技巧
  4. 生态集成:了解ELK Stack的整体解决方案

参考资料

  1. Elasticsearch官方文档
  2. Elasticsearch Java API Client文档
  3. Spring Data Elasticsearch参考指南
  4. Elasticsearch性能调优指南