ElasticSearch全文搜索实战
前言
ElasticSearch是目前最流行的开源搜索引擎,广泛应用于日志分析、全文搜索、数据分析等场景。本文将系统讲解ES的核心概念、使用技巧和最佳实践,帮助你构建高性能的搜索系统。
一、ElasticSearch基础
1.1 核心概念
1 2 3 4 5 6 7
| 索引(Index) ≈ 数据库 类型(Type) ≈ 表(7.x后废弃) 文档(Document) ≈ 行 字段(Field) ≈ 列 映射(Mapping) ≈ 表结构 分片(Shard) = 数据分布单元 副本(Replica) = 数据备份
|
1.2 安装启动
1 2 3 4 5 6 7 8 9 10 11
| docker run -d \ --name elasticsearch \ -p 9200:9200 \ -p 9300:9300 \ -e "discovery.type=single-node" \ -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ elasticsearch:8.8.0
curl http://localhost:9200
|
1.3 基本操作
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
| PUT /products { "settings": { "number_of_shards": 3, "number_of_replicas": 1 } }
POST /products/_doc/1 { "name": "iPhone 15 Pro", "price": 999, "category": "Electronics", "brand": "Apple", "tags": ["smartphone", "5G"], "created_at": "2024-04-08" }
GET /products/_doc/1
POST /products/_update/1 { "doc": { "price": 899 } }
DELETE /products/_doc/1
DELETE /products
|
二、映射(Mapping)设计
2.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
| PUT /products { "mappings": { "properties": { "name": { "type": "text", "analyzer": "ik_max_word", "fields": { "keyword": { "type": "keyword" } } }, "price": { "type": "double" }, "stock": { "type": "integer" }, "category": { "type": "keyword" }, "description": { "type": "text", "analyzer": "ik_smart" }, "tags": { "type": "keyword" }, "location": { "type": "geo_point" }, "created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" }, "is_active": { "type": "boolean" } } } }
|
2.2 动态映射
1 2 3 4 5 6 7 8 9 10 11 12
| PUT /products/_mapping { "dynamic": "strict", "properties": { ... } }
|
三、查询DSL
3.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
| GET /products/_search { "query": { "match": { "name": "iPhone Pro" } } }
GET /products/_search { "query": { "multi_match": { "query": "smartphone", "fields": ["name", "description"] } } }
GET /products/_search { "query": { "term": { "category.keyword": "Electronics" } } }
GET /products/_search { "query": { "range": { "price": { "gte": 500, "lte": 1000 } } } }
|
3.2 复合查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| GET /products/_search { "query": { "bool": { "must": [ { "match": { "name": "iPhone" } } ], "filter": [ { "term": { "category": "Electronics" } }, { "range": { "price": { "lte": 1000 } } } ], "should": [ { "term": { "brand": "Apple" } } ], "must_not": [ { "term": { "is_active": false } } ], "minimum_should_match": 1 } } }
|
3.3 分页和排序
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
| GET /products/_search { "query": { "match_all": {} }, "from": 0, "size": 20, "sort": [ { "price": "asc" }, { "created_at": "desc" }, "_score" ], "_source": ["name", "price"] }
GET /products/_search { "size": 10, "sort": [ { "created_at": "desc" }, { "_id": "desc" } ], "search_after": ["2024-04-08", "100"] }
|
3.4 高亮显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| GET /products/_search { "query": { "match": { "name": "iPhone" } }, "highlight": { "fields": { "name": { "pre_tags": ["<em>"], "post_tags": ["</em>"] } } } }
|
四、聚合分析
4.1 指标聚合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| GET /products/_search { "size": 0, "aggs": { "avg_price": { "avg": { "field": "price" } }, "max_price": { "max": { "field": "price" } }, "min_price": { "min": { "field": "price" } }, "sum_stock": { "sum": { "field": "stock" } }, "stats_price": { "stats": { "field": "price" } } } }
|
4.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
| GET /products/_search { "size": 0, "aggs": { "group_by_category": { "terms": { "field": "category.keyword", "size": 10, "order": { "_count": "desc" } }, "aggs": { "avg_price": { "avg": { "field": "price" } } } } } }
GET /products/_search { "size": 0, "aggs": { "sales_over_time": { "date_histogram": { "field": "created_at", "calendar_interval": "month" }, "aggs": { "total_sales": { "sum": { "field": "price" } } } } } }
GET /products/_search { "size": 0, "aggs": { "price_ranges": { "range": { "field": "price", "ranges": [ { "to": 500 }, { "from": 500, "to": 1000 }, { "from": 1000 } ] } } } }
|
五、中文分词
5.1 安装IK分词器
1 2 3 4 5
| elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.8.0/elasticsearch-analysis-ik-8.8.0.zip
docker restart elasticsearch
|
5.2 分词器测试
1 2 3 4 5 6 7 8 9 10 11 12 13
| POST /_analyze { "analyzer": "ik_max_word", "text": "中华人民共和国国歌" }
POST /_analyze { "analyzer": "ik_smart", "text": "中华人民共和国国歌" }
|
5.3 自定义词典
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IK Analyzer 扩展配置</comment> <entry key="ext_dict">custom.dic</entry> <entry key="ext_stopwords">stopword.dic</entry> </properties>
iPhone 安卓 小米
|
六、性能优化
6.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
| PUT /products/_settings { "index": { "refresh_interval": "30s", "number_of_replicas": 1, "max_result_window": 10000, "routing": { "allocation": { "total_shards_per_node": 2 } } } }
PUT /products/_settings { "refresh_interval": "-1", "number_of_replicas": 0 }
PUT /products/_settings { "refresh_interval": "1s", "number_of_replicas": 1 } POST /products/_forcemerge
|
6.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
| GET /products/_search { "query": { "bool": { "filter": [ { "term": { "category": "Electronics" } } ] } } }
GET /products/_search { "query": { "bool": { "filter": [ { "match": { "name": "iPhone" } } ] } }, "track_scores": false }
GET /products/_search { "_source": { "includes": ["name", "price"], "excludes": ["description"] } }
|
6.3 批量操作
1 2 3 4 5 6 7 8 9
| POST /_bulk { "index": { "_index": "products", "_id": "1" } } { "name": "Product 1", "price": 100 } { "index": { "_index": "products", "_id": "2" } } { "name": "Product 2", "price": 200 } { "update": { "_index": "products", "_id": "1" } } { "doc": { "price": 150 } } { "delete": { "_index": "products", "_id": "3" } }
|
七、Java客户端
7.1 Spring Data ElasticSearch
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
|
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
| @Document(indexName = "products") public class Product { @Id private String id; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String name; @Field(type = FieldType.Double) private Double price; @Field(type = FieldType.Keyword) private String category; @Field(type = FieldType.Date, format = DateFormat.date_time) private Date createdAt; }
public interface ProductRepository extends ElasticsearchRepository<Product, String> { List<Product> findByName(String name); List<Product> findByPriceBetween(Double min, Double max); List<Product> findByCategoryAndPriceGreaterThan(String category, Double price); }
@Service public class ProductService { @Autowired private ProductRepository productRepository; @Autowired private ElasticsearchRestTemplate elasticsearchTemplate; public Product save(Product product) { return productRepository.save(product); } public List<Product> search(String keyword) { Criteria criteria = new Criteria("name").matches(keyword) .or("description").matches(keyword); Query query = new CriteriaQuery(criteria); SearchHits<Product> hits = elasticsearchTemplate.search(query, Product.class); return hits.stream() .map(SearchHit::getContent) .collect(Collectors.toList()); } public Page<Product> complexSearch(String keyword, String category, Double minPrice) { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); if (keyword != null) { boolQuery.must(QueryBuilders.multiMatchQuery(keyword, "name", "description")); } if (category != null) { boolQuery.filter(QueryBuilders.termQuery("category", category)); } if (minPrice != null) { boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice)); } NativeSearchQuery query = new NativeSearchQueryBuilder() .withQuery(boolQuery) .withPageable(PageRequest.of(0, 10)) .withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)) .build(); SearchHits<Product> hits = elasticsearchTemplate.search(query, Product.class); return SearchHitSupport.searchPageFor(hits, query.getPageable()); } }
|
八、实战案例
8.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
| GET /products/_search { "query": { "bool": { "must": [ { "multi_match": { "query": "iPhone 手机", "fields": ["name^3", "description"], # name权重3倍 "type": "best_fields", "minimum_should_match": "75%" } } ], "filter": [ { "term": { "category": "手机" } }, { "range": { "price": { "gte": 3000, "lte": 8000 } } }, { "term": { "brand": "苹果" } } ] } }, "aggs": { "price_stats": { "stats": { "field": "price" } }, "brand_count": { "terms": { "field": "brand.keyword" } } }, "sort": [ { "_score": "desc" }, { "sales": "desc" }, { "created_at": "desc" } ], "highlight": { "fields": { "name": {}, "description": {} } } }
|
8.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
| PUT /logs-2024.04 { "mappings": { "properties": { "timestamp": { "type": "date" }, "level": { "type": "keyword" }, "message": { "type": "text" }, "service": { "type": "keyword" }, "host": { "type": "keyword" }, "trace_id": { "type": "keyword" } } } }
GET /logs-*/_search { "query": { "bool": { "filter": [ { "term": { "level": "ERROR" } }, { "range": { "timestamp": { "gte": "now-1h" } } } ] } }, "aggs": { "errors_by_service": { "terms": { "field": "service" } }, "errors_over_time": { "date_histogram": { "field": "timestamp", "calendar_interval": "1m" } } } }
|
九、集群管理
9.1 集群健康
1 2 3 4 5 6 7 8 9 10 11
| GET /_cluster/health
GET /_cat/nodes?v
GET /_cat/indices?v
GET /_cat/shards?v
|
9.2 索引别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| POST /_aliases { "actions": [ { "add": { "index": "products_v1", "alias": "products" } } ] }
POST /_aliases { "actions": [ { "remove": { "index": "products_v1", "alias": "products" } }, { "add": { "index": "products_v2", "alias": "products" } } ] }
|
十、总结
ElasticSearch核心要点:
关键概念
- 倒排索引:快速全文搜索的核心
- 分片和副本:分布式存储和高可用
- 分词器:中文搜索的关键
- 聚合分析:强大的数据分析能力
最佳实践
- 合理设计Mapping
- 使用filter代替query
- 控制分片数量
- 定期优化索引
- 监控集群健康
掌握ElasticSearch,让你的搜索功能飞起来!
参考资料