ElasticSearch复习指南:从零搭建一个商品搜索案例
前言:为什么我们需要ElasticSearch?
想象一下,你正在开发一个电商网站,商品数量达到百万级别。用户需要能够快速搜索商品,并且希望支持模糊匹配、按价格筛选、按分类过滤等功能。
如果使用传统的数据库(如MySQL)来实现,你可能会写出这样的查询:
SELECT * FROM products
WHERE name LIKE '%手机%'
AND price BETWEEN 1000 AND 5000
AND category_id = 1
ORDER BY create_time DESC
LIMIT 20 OFFSET 0;
随着数据量增加,这种查询会变得越来越慢,而且无法很好地支持模糊搜索和相关度排序。这就是ElasticSearch的用武之地。
一、ElasticSearch核心概念快速回顾
1.1 倒排索引:为什么ES这么快?
传统数据库使用"正排索引":文档→关键词
文档1:我爱学习ElasticSearch
文档2:ElasticSearch真强大
而ElasticSearch使用"倒排索引":关键词→文档
我: [文档1]
爱: [文档1]
学习: [文档1]
ElasticSearch: [文档1, 文档2]
真: [文档2]
强大: [文档2]
这样搜索"ElasticSearch"时,直接就能找到文档1和文档2,无需扫描所有文档。
1.2 基本概念对照表
ElasticSearch术语 | 传统数据库类比 | 说明 |
---|---|---|
Index(索引) | Database(数据库) | 数据容器 |
Type(类型) | Table(表) | ES7+已废弃 |
Document(文档) | Row(行) | 基本数据单位 |
Field(字段) | Column(列) | 数据字段 |
Mapping(映射) | Schema(模式) | 数据结构定义 |
二、实战案例:搭建商品搜索系统
让我们通过一个真实案例来复习ElasticSearch的使用。
2.1 环境准备
首先使用Docker启动ElasticSearch和Kibana:
# 启动ElasticSearch
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.17.0# 启动Kibana(Web管理界面)
docker run -d --name kibana --link elasticsearch:elasticsearch -p 5601:5601 kibana:7.17.0
2.2 创建商品索引
假设我们的商品有以下字段:
- id:商品ID
- name:商品名称
- price:价格
- category:分类
- description:描述
- tags:标签数组
PUT /products
{"mappings": {"properties": {"id": { "type": "integer" },"name": { "type": "text","analyzer": "ik_max_word", // 使用中文分词器"search_analyzer": "ik_smart"},"price": { "type": "float" },"category": { "type": "keyword" },"description": { "type": "text","analyzer": "ik_max_word"},"tags": { "type": "keyword" },"created_at": { "type": "date" }}}
}
2.3 插入测试数据
POST /products/_bulk
{"index":{"_id":1}}
{"id":1,"name":"Apple iPhone 13","price":5999,"category":"手机","description":"新一代iPhone,A15芯片,超视网膜XDR显示屏","tags":["苹果","智能手机","5G"],"created_at":"2022-01-01"}
{"index":{"_id":2}}
{"id":2,"name":"华为Mate 50","price":5499,"category":"手机","description":"华为旗舰手机,鸿蒙系统,超强拍照","tags":["华为","智能手机","5G"],"created_at":"2022-02-01"}
{"index":{"_id":3}}
{"id":3,"name":"小米手环7","price":249,"category":"智能穿戴","description":"1.62英寸AMOLED屏,120种运动模式","tags":["小米","智能手环","健康监测"],"created_at":"2022-03-01"}
2.4 实现复杂搜索功能
案例1:基本搜索 - 查找商品名称或描述中包含"手机"的商品
GET /products/_search
{"query": {"multi_match": {"query": "手机","fields": ["name", "description"]}}
}
案例2:多条件过滤 - 搜索手机分类中,价格在5000-6000之间的商品
GET /products/_search
{"query": {"bool": {"must": [{ "match": { "category": "手机" } }],"filter": [{ "range": { "price": { "gte": 5000, "lte": 6000 } } }]}}
}
案例3:模糊搜索与评分 - 搜索"华为"(用户输入错误)
GET /products/_search
{"query": {"fuzzy": {"name": {"value": "华为","fuzziness": "AUTO"}}}
}
案例4:聚合分析 - 统计每个分类的商品数量和平均价格
GET /products/_search
{"size": 0,"aggs": {"categories": {"terms": {"field": "category"},"aggs": {"avg_price": {"avg": {"field": "price"}}}}}
}
三、性能优化技巧
3.1 索引设计优化
合理使用字段类型:
- 精确匹配用
keyword
- 文本搜索用
text
+适当的分词器
- 精确匹配用
索引分片策略:
PUT /products {"settings": {"number_of_shards": 3, // 主分片数,一旦设置不能修改"number_of_replicas": 1 // 副本分片数,可随时调整} }
3.2 查询优化
- 避免深度分页:使用
search_after
代替from/size
- 使用过滤器上下文:filter不计算评分,性能更好
- 合理使用索引别名:实现零停机重建索引
// 1. 创建新索引
PUT /products_new// 2. 将数据从旧索引迁移到新索引
POST _reindex
{"source": { "index": "products" },"dest": { "index": "products_new" }
}// 3. 原子操作切换别名
POST _aliases
{"actions": [{ "remove": { "index": "products", "alias": "products_alias" } },{ "add": { "index": "products_new", "alias": "products_alias" } }]
}
四、常见问题与解决方案
4.1 数据同步问题:如何保证数据库与ES数据一致?
推荐使用两种方案:
双写模式:应用代码中同时写入数据库和ES
- 优点:简单直接
- 缺点:可能存在数据不一致
日志订阅模式:通过CDC工具(如Canal、Debezium)捕获数据库变更
- 优点:解耦,保证最终一致性
- 缺点:架构复杂
4.2 中文分词问题
ES默认的中文分词效果不好,推荐使用IK分词器:
PUT /products
{"settings": {"analysis": {"analyzer": {"ik_smart_custom": {"type": "custom","tokenizer": "ik_smart"}}}}
}
五、总结
通过这个商品搜索案例,我们复习了ElasticSearch的核心概念和实际应用:
- 倒排索引是ES高性能搜索的基石
- 合理的Mapping设计对搜索性能和准确性至关重要
- 复合查询可以满足复杂的业务需求
- 聚合分析提供了强大的数据分析能力
- 性能优化需要从索引设计和查询两方面入手