【大数据】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调优、索引调优、查询调优)
- 监控与运维(健康检查、性能监控、日志分析)
- 备份与恢复策略
- 企业级最佳实践
学习建议
🎯 学习顺序
- 从入门级开始:即使有一定基础,也建议先阅读入门级教程,确保概念理解准确
- 循序渐进:按照入门→进阶→高级的顺序学习,每个级别都有前一级别的知识基础
- 实践为主:每个教程都提供了完整的代码示例,建议边学边练
- 项目实战:学完每个级别后,尝试在实际项目中应用所学知识
💡 学习技巧
- 环境准备:建议使用Docker搭建学习环境,方便快速部署和重置
- 代码实践:所有示例代码都经过测试,可以直接运行
- 文档查阅:结合官方文档学习,加深对API和配置的理解
- 社区交流:遇到问题时,可以在Elasticsearch社区寻求帮助
📖 扩展学习
完成本系列教程后,建议继续学习:
- ELK Stack:学习Logstash和Kibana,构建完整的日志分析解决方案
- Beats系列:掌握各种数据采集工具
- 机器学习:了解Elasticsearch的ML功能
- 云服务:学习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
| 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
| 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
| 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
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>co.elastic.clients</groupId> <artifactId>elasticsearch-java</artifactId> <version>8.12.0</version> </dependency> <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;
@Bean public ElasticsearchClient elasticsearchClient() { RestClient restClient = RestClient.builder( new HttpHost(host, port, "http") ).build(); 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") ))) .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
|
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<>(); } 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") .type(TextQueryType.BestFields) ) ) .highlight(h -> h .fields("title", hf -> hf) .fields("content", hf -> hf) ) .size(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) ) ) ); 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
|
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; final int THREAD_COUNT = 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 .multiMatch(m -> m .query(params.getKeyword()) .fields("title^3", "content") .type(TextQueryType.BestFields) .minimumShouldMatch("75%") ) ) .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; }
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; } }
|
六、实战案例
(一)电商搜索系统
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 在以下方面表现出色:
- 强大的搜索能力:支持全文搜索、结构化搜索、地理位置搜索等
- 高性能:分布式架构,支持水平扩展,处理PB级数据
- 实时性:近实时搜索和分析能力
- 易用性:RESTful API,丰富的客户端支持
- 生态完整:与ELK Stack无缝集成
(二)最佳实践总结
1. 索引设计原则
合理的索引设计要点:
- 分片数量设置 - 通常每个分片控制在20-40GB之间
- 映射类型选择 - 使用合适的映射类型和分析器
- 结构设计 - 避免过深的嵌套结构,影响查询性能
- 字段类型 - 合理使用keyword和text类型,根据查询需求选择
2. 性能优化要点
- 批量操作:使用bulk API进行批量插入和更新
- 查询优化:使用filter替代query进行精确匹配
- 内存管理:合理设置JVM堆内存大小
- 监控告警:建立完善的监控体系
3. 运维建议
- 定期维护:定期清理过期数据,优化索引
- 备份策略:建立完善的快照备份机制
- 安全配置:启用安全功能,配置用户权限
- 版本管理:及时更新到稳定版本
(三)学习建议
- 基础概念:深入理解Elasticsearch的核心概念和架构
- 实践项目:通过实际项目加深理解
- 性能调优:学习性能监控和调优技巧
- 生态集成:了解ELK Stack的整体解决方案
参考资料
- Elasticsearch官方文档
- Elasticsearch Java API Client文档
- Spring Data Elasticsearch参考指南
- Elasticsearch性能调优指南