前言
在掌握了Elasticsearch的基础概念和简单操作后,本文将深入探讨复杂查询、聚合分析和性能优化等进阶主题。这些技能是构建高效搜索应用的关键,适合有一定Elasticsearch基础的开发者学习。
系列文章导航
一、复杂查询技术
(一)布尔查询
1. 布尔查询基础
布尔查询是Elasticsearch中最强大的查询类型,允许组合多个查询条件。
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
| @Service public class AdvancedSearchService { @Autowired private ElasticsearchClient client;
public List<Document> booleanSearch(String indexName, SearchParams params) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q .bool(b -> b .must(must -> must .multiMatch(m -> m .query(params.getKeyword()) .fields("title^2", "content") .type(TextQueryType.BestFields) ) ) .filter(filter -> filter .range(r -> r .field("createTime") .gte(JsonData.of(params.getStartTime())) .lte(JsonData.of(params.getEndTime())) ) ) .should(should -> should .terms(t -> t .field("tags") .terms(TermsQueryField.of(tf -> tf.value( params.getTags().stream() .map(FieldValue::of) .collect(Collectors.toList()) ))) ) ) .mustNot(mustNot -> mustNot .term(t -> t .field("status") .value("deleted") ) ) .minimumShouldMatch("1") ) ) .sort(so -> so .score(sc -> sc.order(SortOrder.Desc)) .field(f -> f.field("createTime").order(SortOrder.Desc)) ) .size(20) ); SearchResponse<Document> response = client.search(request, Document.class); return response.hits().hits().stream() .map(Hit::source) .collect(Collectors.toList()); } }
|
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
|
public class SearchParams { private String keyword; private String startTime; private String endTime; private List<String> tags; private String category; private Integer from = 0; private Integer size = 20; public SearchParams() {} public SearchParams(String keyword) { this.keyword = keyword; } public String getKeyword() { return keyword; } public void setKeyword(String keyword) { this.keyword = keyword; } public String getStartTime() { return startTime; } public void setStartTime(String startTime) { this.startTime = startTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public List<String> getTags() { return tags != null ? tags : new ArrayList<>(); } public void setTags(List<String> tags) { this.tags = tags; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } public Integer getFrom() { return from; } public void setFrom(Integer from) { this.from = from; } public Integer getSize() { return size; } public void setSize(Integer size) { this.size = 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
|
public SearchResult advancedFullTextSearch(String indexName, String keyword, int page, int size) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q .bool(b -> b .should(should -> should .match(m -> m .field("title") .query(keyword) .boost(3.0f) ) ) .should(should -> should .match(m -> m .field("title") .query(keyword) .fuzziness("AUTO") .boost(2.0f) ) ) .should(should -> should .match(m -> m .field("content") .query(keyword) .boost(1.0f) ) ) .should(should -> should .matchPhrase(mp -> mp .field("content") .query(keyword) .boost(2.5f) ) ) .minimumShouldMatch("1") ) ) .highlight(h -> h .fields("title", hf -> hf .preTags("<mark>") .postTags("</mark>") .numberOfFragments(1) ) .fields("content", hf -> hf .preTags("<mark>") .postTags("</mark>") .fragmentSize(150) .numberOfFragments(3) ) ) .from(page * size) .size(size) .sort(so -> so .score(sc -> sc.order(SortOrder.Desc)) ) ); SearchResponse<Document> response = client.search(request, Document.class); List<SearchResultItem> items = new ArrayList<>(); for (Hit<Document> hit : response.hits().hits()) { SearchResultItem item = new SearchResultItem(); item.setDocument(hit.source()); item.setScore(hit.score()); if (hit.highlight() != null) { Map<String, List<String>> highlights = new HashMap<>(); hit.highlight().forEach((field, fragments) -> { highlights.put(field, fragments); }); item.setHighlights(highlights); } items.add(item); } return new SearchResult( items, response.hits().total().value(), page, size ); }
|
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
|
public class SearchResultItem { private Document document; private Double score; private Map<String, List<String>> highlights; public SearchResultItem() {} public Document getDocument() { return document; } public void setDocument(Document document) { this.document = document; } public Double getScore() { return score; } public void setScore(Double score) { this.score = score; } public Map<String, List<String>> getHighlights() { return highlights; } public void setHighlights(Map<String, List<String>> highlights) { this.highlights = highlights; } }
public class SearchResult { private List<SearchResultItem> items; private Long total; private Integer page; private Integer size; public SearchResult(List<SearchResultItem> items, Long total, Integer page, Integer size) { this.items = items; this.total = total; this.page = page; this.size = size; } public List<SearchResultItem> getItems() { return items; } public Long getTotal() { return total; } public Integer getPage() { return page; } public Integer getSize() { return size; } public Integer getTotalPages() { return (int) Math.ceil((double) total / 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
|
public List<Document> rangeSearch(String indexName, RangeParams params) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q .bool(b -> { BoolQuery.Builder boolBuilder = new BoolQuery.Builder(); if (params.getMinPrice() != null || params.getMaxPrice() != null) { boolBuilder.filter(filter -> filter .range(r -> { RangeQuery.Builder rangeBuilder = new RangeQuery.Builder() .field("price"); if (params.getMinPrice() != null) { rangeBuilder.gte(JsonData.of(params.getMinPrice())); } if (params.getMaxPrice() != null) { rangeBuilder.lte(JsonData.of(params.getMaxPrice())); } return rangeBuilder.build(); }) ); } if (params.getStartDate() != null || params.getEndDate() != null) { boolBuilder.filter(filter -> filter .range(r -> { RangeQuery.Builder rangeBuilder = new RangeQuery.Builder() .field("createTime"); if (params.getStartDate() != null) { rangeBuilder.gte(JsonData.of(params.getStartDate())); } if (params.getEndDate() != null) { rangeBuilder.lte(JsonData.of(params.getEndDate())); } return rangeBuilder.build(); }) ); } return boolBuilder.build(); }) ) .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()); }
|
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
|
public List<Document> fuzzyAndWildcardSearch(String indexName, String keyword) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .query(q -> q .bool(b -> b .should(should -> should .fuzzy(f -> f .field("title") .value(keyword) .fuzziness("AUTO") .boost(2.0f) ) ) .should(should -> should .wildcard(w -> w .field("title") .value("*" + keyword + "*") .boost(1.5f) ) ) .should(should -> should .prefix(p -> p .field("title") .value(keyword) .boost(1.0f) ) ) .minimumShouldMatch("1") ) ) ); 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
| @Service public class AggregationService { @Autowired private ElasticsearchClient client;
public Map<String, Long> getTagsAggregation(String indexName, int size) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .size(0) .aggregations("tags_agg", a -> a .terms(t -> t .field("tags") .size(size) .order(NamedValue.of("_count", SortOrder.Desc)) ) ) ); SearchResponse<Void> response = client.search(request, Void.class); Map<String, Long> tagCounts = new LinkedHashMap<>(); 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> getCategoryAggregation(String indexName) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .size(0) .aggregations("category_agg", a -> a .terms(t -> t .field("category.keyword") .size(20) ) ) ); SearchResponse<Void> response = client.search(request, Void.class); Map<String, Long> categoryCounts = new LinkedHashMap<>(); StringTermsAggregate categoryAgg = response.aggregations() .get("category_agg") .sterms(); for (StringTermsBucket bucket : categoryAgg.buckets().array()) { categoryCounts.put(bucket.key().stringValue(), bucket.docCount()); } return categoryCounts; } }
|
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
|
public Map<String, Long> getDateHistogram(String indexName, String interval) throws IOException { CalendarInterval calendarInterval; switch (interval.toLowerCase()) { case "day": calendarInterval = CalendarInterval.Day; break; case "week": calendarInterval = CalendarInterval.Week; break; case "month": calendarInterval = CalendarInterval.Month; break; default: calendarInterval = CalendarInterval.Day; } SearchRequest request = SearchRequest.of(s -> s .index(indexName) .size(0) .aggregations("date_histogram", a -> a .dateHistogram(d -> d .field("createTime") .calendarInterval(calendarInterval) .format("yyyy-MM-dd") .minDocCount(0) ) ) ); 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 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
|
public StatisticsResult getStatistics(String indexName, String field) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .size(0) .aggregations("stats", a -> a .stats(st -> st.field(field)) ) .aggregations("extended_stats", a -> a .extendedStats(es -> es.field(field)) ) ); SearchResponse<Void> response = client.search(request, Void.class); StatsAggregate stats = response.aggregations() .get("stats") .stats(); ExtendedStatsAggregate extendedStats = response.aggregations() .get("extended_stats") .extendedStats(); return new StatisticsResult( stats.count(), stats.sum(), stats.avg(), stats.min(), stats.max(), extendedStats.stdDeviation(), extendedStats.variance() ); }
public class StatisticsResult { private long count; private double sum; private double avg; private double min; private double max; private double stdDeviation; private double variance; public StatisticsResult(long count, double sum, double avg, double min, double max, double stdDeviation, double variance) { this.count = count; this.sum = sum; this.avg = avg; this.min = min; this.max = max; this.stdDeviation = stdDeviation; this.variance = variance; } public long getCount() { return count; } public double getSum() { return sum; } public double getAvg() { return avg; } public double getMin() { return min; } public double getMax() { return max; } public double getStdDeviation() { return stdDeviation; } public double getVariance() { return variance; } }
|
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
|
public Map<String, Double> getPercentiles(String indexName, String field) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .size(0) .aggregations("percentiles", a -> a .percentiles(p -> p .field(field) .percents(25.0, 50.0, 75.0, 90.0, 95.0, 99.0) ) ) ); SearchResponse<Void> response = client.search(request, Void.class); PercentilesAggregate percentiles = response.aggregations() .get("percentiles") .percentiles(); Map<String, Double> result = new LinkedHashMap<>(); for (Percentile percentile : percentiles.values().keyed().values()) { result.put("P" + percentile.percentile(), percentile.value()); } return 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
|
public Map<String, Map<String, Long>> getNestedAggregation(String indexName) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(indexName) .size(0) .aggregations("category_agg", a -> a .terms(t -> t .field("category.keyword") .size(10) ) .aggregations("tags_agg", subA -> subA .terms(t -> t .field("tags") .size(5) ) ) ) ); SearchResponse<Void> response = client.search(request, Void.class); Map<String, Map<String, Long>> result = new LinkedHashMap<>(); StringTermsAggregate categoryAgg = response.aggregations() .get("category_agg") .sterms(); for (StringTermsBucket categoryBucket : categoryAgg.buckets().array()) { String category = categoryBucket.key().stringValue(); Map<String, Long> tagCounts = new LinkedHashMap<>(); StringTermsAggregate tagsAgg = categoryBucket.aggregations() .get("tags_agg") .sterms(); for (StringTermsBucket tagBucket : tagsAgg.buckets().array()) { tagCounts.put(tagBucket.key().stringValue(), tagBucket.docCount()); } result.put(category, tagCounts); } return 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
|
@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", "summary", "createTime", "tags") .excludes("content") ) ) ); SearchResponse<Document> response = client.search(request, Document.class); List<SearchResultItem> items = response.hits().hits().stream() .map(hit -> { SearchResultItem item = new SearchResultItem(); item.setDocument(hit.source()); item.setScore(hit.score()); return item; }) .collect(Collectors.toList()); return new SearchResult( items, response.hits().total().value(), params.getFrom() / params.getSize(), params.getSize() ); } }
|
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
|
@Service public class OptimizedBulkService { @Autowired private ElasticsearchClient client;
public void optimizedBulkInsert(String indexName, List<Document> documents) throws IOException { final int BATCH_SIZE = 1000; 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); processBatch(indexName, batch); if (endIndex < documents.size()) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } } 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("批次处理存在错误"); for (BulkResponseItem item : response.items()) { if (item.error() != null) { System.err.println("错误: " + item.error().reason()); } } } else { System.out.println("成功处理批次,文档数量: " + batch.size()); } } }
|
(二)索引优化
1. 索引设置优化
1 2 3 4 5 6 7 8 9 10 11
|
indices.memory.index_buffer_size: 20% indices.memory.min_index_buffer_size: 96mb
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
|
public void createOptimizedIndex(String indexName) throws IOException { TypeMapping mapping = TypeMapping.of(m -> m .properties("id", Property.of(p -> p.keyword(k -> k .store(false) ))) .properties("title", Property.of(p -> p.text(t -> t .analyzer("standard") .store(true) .fields("keyword", f -> f.keyword(k -> k)) ))) .properties("content", Property.of(p -> p.text(t -> t .analyzer("standard") .store(false) .indexOptions(IndexOptions.Docs) ))) .properties("createTime", Property.of(p -> p.date(d -> d .format("yyyy-MM-dd HH:mm:ss") .store(false) ))) .properties("tags", Property.of(p -> p.keyword(k -> k .store(false) ))) .properties("summary", Property.of(p -> p.text(t -> t .analyzer("standard") .store(true) ))) ); CreateIndexRequest request = CreateIndexRequest.of(i -> i .index(indexName) .mappings(mapping) .settings(s -> s .numberOfShards("3") .numberOfReplicas("1") .refreshInterval(t -> t.time("30s")) .maxResultWindow(50000) ) ); CreateIndexResponse response = client.indices().create(request); System.out.println("优化索引创建结果: " + response.acknowledged()); }
|
四、实战案例:电商搜索系统
(一)商品搜索实现
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
|
public class Product { private String id; private String name; private String description; private String category; private String brand; private Double price; private Integer stock; private List<String> tags; private Double rating; private Integer reviewCount; private String createTime; public Product() {} public Product(String id, String name, String category, String brand, Double price) { this.id = id; this.name = name; this.category = category; this.brand = brand; this.price = price; this.createTime = LocalDateTime.now().format( DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") ); this.tags = new ArrayList<>(); } }
|
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 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 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
| @Service public class ProductSearchService { @Autowired private ElasticsearchClient client; private static final String PRODUCT_INDEX = "products";
public SearchResult searchProducts(ProductSearchParams params) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(PRODUCT_INDEX) .query(q -> q .bool(b -> { BoolQuery.Builder boolBuilder = new BoolQuery.Builder(); if (StringUtils.hasText(params.getKeyword())) { boolBuilder.must(must -> must .multiMatch(m -> m .query(params.getKeyword()) .fields("name^3", "description", "brand^2") .type(TextQueryType.BestFields) .fuzziness("AUTO") ) ); } if (StringUtils.hasText(params.getCategory())) { boolBuilder.filter(filter -> filter .term(t -> t .field("category.keyword") .value(params.getCategory()) ) ); } if (params.getBrands() != null && !params.getBrands().isEmpty()) { boolBuilder.filter(filter -> filter .terms(t -> t .field("brand.keyword") .terms(TermsQueryField.of(tf -> tf.value( params.getBrands().stream() .map(FieldValue::of) .collect(Collectors.toList()) ))) ) ); } if (params.getMinPrice() != null || params.getMaxPrice() != null) { boolBuilder.filter(filter -> filter .range(r -> { RangeQuery.Builder rangeBuilder = new RangeQuery.Builder() .field("price"); if (params.getMinPrice() != null) { rangeBuilder.gte(JsonData.of(params.getMinPrice())); } if (params.getMaxPrice() != null) { rangeBuilder.lte(JsonData.of(params.getMaxPrice())); } return rangeBuilder.build(); }) ); } if (params.getMinRating() != null) { boolBuilder.filter(filter -> filter .range(r -> r .field("rating") .gte(JsonData.of(params.getMinRating())) ) ); } boolBuilder.filter(filter -> filter .range(r -> r .field("stock") .gt(JsonData.of(0)) ) ); return boolBuilder.build(); }) ) .sort(so -> { switch (params.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 "rating": return so.field(f -> f.field("rating").order(SortOrder.Desc)); case "newest": return so.field(f -> f.field("createTime").order(SortOrder.Desc)); default: return so.score(sc -> sc.order(SortOrder.Desc)); } }) .from(params.getFrom()) .size(params.getSize()) .highlight(h -> h .fields("name", hf -> hf .preTags("<mark>") .postTags("</mark>") ) .fields("description", hf -> hf .preTags("<mark>") .postTags("</mark>") .fragmentSize(100) .numberOfFragments(2) ) ) ); SearchResponse<Product> response = client.search(request, Product.class); List<SearchResultItem> items = new ArrayList<>(); for (Hit<Product> hit : response.hits().hits()) { SearchResultItem item = new SearchResultItem(); item.setDocument(hit.source()); item.setScore(hit.score()); if (hit.highlight() != null) { Map<String, List<String>> highlights = new HashMap<>(); hit.highlight().forEach((field, fragments) -> { highlights.put(field, fragments); }); item.setHighlights(highlights); } items.add(item); } return new SearchResult( items, response.hits().total().value(), params.getFrom() / params.getSize(), params.getSize() ); }
public List<String> getSuggestions(String prefix) throws IOException { SearchRequest request = SearchRequest.of(s -> s .index(PRODUCT_INDEX) .suggest(suggest -> suggest .suggesters("product_suggest", suggester -> suggester .prefix(prefix) .completion(c -> c .field("name.suggest") .size(10) ) ) ) ); SearchResponse<Product> response = client.search(request, Product.class); List<String> suggestions = new ArrayList<>(); if (response.suggest() != null) { CompletionSuggest completionSuggest = response.suggest() .get("product_suggest") .get(0) .completion(); for (CompletionSuggestOption option : completionSuggest.options()) { suggestions.add(option.text()); } } return suggestions; } }
|
五、总结与下一步
(一)本文总结
通过本文的学习,您已经掌握了:
- 复杂查询:布尔查询、全文搜索、范围查询、模糊查询等高级查询技术
- 聚合分析:桶聚合、指标聚合、嵌套聚合等数据分析方法
- 性能优化:查询优化、索引优化、批量操作优化等基础优化技术
- 实战应用:电商搜索系统的完整实现
(二)学习建议
进阶阶段重点:
- 熟练掌握各种复杂查询的组合使用
- 理解聚合分析的原理和应用场景
- 掌握基础的性能优化技巧
- 能够设计和实现完整的搜索功能
实践建议:
- 构建完整的搜索应用项目
- 尝试不同的查询组合和聚合分析
- 测试和优化查询性能
- 学习使用Elasticsearch的监控工具
(三)下一步学习
完成进阶学习后,建议继续学习:
🔴 高级教程:
- 集群架构设计与管理
- 深度性能调优和监控
- 企业级安全配置
- 大规模数据处理方案
(四)参考资料
系列文章导航