Elasticsearch 常用功能全覆盖:从基础概念到项目实战
Elasticsearch,简称 ES,是一个基于 Lucene 构建的分布式搜索与分析引擎。
很多人第一次接触 Elasticsearch 时,最容易产生一个误区:
Elasticsearch 是不是一个比 MySQL 更快的数据库?
答案是否定的。
Elasticsearch 的核心定位不是替代 MySQL,而是解决传统关系型数据库不擅长解决的问题,例如:
- 全文检索
- 模糊搜索
- 分词搜索
- 多条件复杂筛选
- 搜索相关性排序
- 搜索结果高亮
- 海量日志检索
- 聚合统计分析
在真实项目中,最常见的架构往往是:
MySQL
│
│ 数据同步
▼
Elasticsearch
│
│ 搜索
▼
用户
也就是说:
MySQL = 核心业务数据
Elasticsearch = 搜索索引
例如商品系统中:
商品创建
商品修改
商品库存
订单交易
通常还是以 MySQL 为准。
而:
商品搜索
商品筛选
关键词检索
销量排序
价格区间
品牌聚合
则非常适合 Elasticsearch。
一、为什么需要 Elasticsearch?
假设我们有一张商品表:
CREATE TABLE product (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
description TEXT,
brand VARCHAR(100),
category VARCHAR(100),
price DECIMAL(10,2),
sales INT
);
用户搜索:
苹果手机
最简单的 MySQL 写法可能是:
SELECT *
FROM product
WHERE name LIKE '%苹果手机%'
OR description LIKE '%苹果手机%';
数据量小时,这种方式还能使用。
但是当数据越来越多,并且搜索需求逐渐变成:
关键词:苹果手机
品牌:Apple
分类:手机
价格:5000~10000
销量:从高到低
支持中文分词
支持高亮
支持相关度排序
MySQL 就会越来越吃力。
而 Elasticsearch 天生就是为这种场景设计的。
二、Elasticsearch 为什么搜索快?
Elasticsearch 最重要的底层思想之一叫:
倒排索引
传统数据库可以简单理解成:
文档1 -> 苹果手机
文档2 -> 华为手机
文档3 -> 小米电视
Elasticsearch 会建立类似这样的结构:
苹果 -> 文档1
手机 -> 文档1、文档2
华为 -> 文档2
小米 -> 文档3
电视 -> 文档3
如果用户搜索:
手机
Elasticsearch 不需要遍历全部数据。
它可以直接找到:
手机 -> 文档1、文档2
这就是倒排索引。
因此,ES 特别擅长:
从大量文本中寻找包含某些关键词的数据
三、Elasticsearch 中几个最重要的概念
学习 ES 前,需要先理解几个基本概念。
1. Index
Index 可以理解成 Elasticsearch 中的一类数据集合。
例如:
products
users
articles
orders
logs
可以粗略类比 MySQL:
MySQL Table
≈
Elasticsearch Index
但是两者底层设计完全不同,因此只能作为理解上的类比。
2. Document
Document 就是 Elasticsearch 中的一条数据。
例如:
{
"id": 1001,
"name": "Apple iPhone 16 Pro",
"brand": "Apple",
"price": 8999
}
可以粗略理解为数据库中的一行。
3. Field
Document 中的字段:
{
"id": 1001,
"name": "Apple iPhone 16 Pro",
"price": 8999
}
这里:
id
name
price
就是 Field。
4. Mapping
Mapping 用来定义字段的数据类型以及字段如何建立索引。
可以类比数据库的表结构。
例如:
PUT /products
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text"
},
"brand": {
"type": "keyword"
},
"price": {
"type": "double"
}
}
}
}
四、text 和 keyword 是 ES 最重要的字段区别之一
初学 Elasticsearch,必须真正理解:
text
和:
keyword
的区别。
text
text 表示:
这个字段需要进行全文搜索。
例如:
"name": {
"type": "text"
}
假设:
Apple iPhone 16 Pro
ES 会对它进行分析和分词。
之后用户搜索:
iphone
也有机会搜索出来。
所以通常:
商品名称
商品描述
文章标题
文章正文
使用:
text
keyword
keyword 表示:
整个字段被当成一个完整值。
例如:
"brand": {
"type": "keyword"
}
适合:
品牌
状态
类型
SKU
用户名
标签
订单号
这些需要精确匹配的字段。
text + keyword
真实项目中经常会看到:
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
这时候 Elasticsearch 会同时提供:
name
和:
name.keyword
两个访问方式。
其中:
name
适合全文搜索。
而:
name.keyword
适合:
精确匹配
排序
聚合
这是 Elasticsearch 中非常常见的设计。
五、创建索引
创建商品索引:
PUT /products
{
"mappings": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text"
},
"description": {
"type": "text"
},
"brandId": {
"type": "long"
},
"brandName": {
"type": "keyword"
},
"categoryId": {
"type": "long"
},
"categoryName": {
"type": "keyword"
},
"price": {
"type": "double"
},
"sales": {
"type": "integer"
},
"status": {
"type": "integer"
},
"createTime": {
"type": "date"
}
}
}
}
六、查看索引
查看索引:
GET /products
查看 Mapping:
GET /products/_mapping
七、删除索引
DELETE /products
需要特别注意:
删除索引相当于直接删除:
索引结构
+
索引中的全部数据
生产环境操作时必须谨慎。
八、新增 Document
新增商品:
POST /products/_doc/1001
{
"id": 1001,
"name": "Apple iPhone 16 Pro",
"description": "Apple flagship smartphone",
"brandId": 1,
"brandName": "Apple",
"categoryId": 10,
"categoryName": "Phone",
"price": 8999,
"sales": 12000,
"status": 1
}
这里:
1001
就是 Document ID。
九、根据 ID 查询
GET /products/_doc/1001
这属于精确读取。
但是这里要注意:
如果业务只是:
根据主键 ID 查询一条数据
通常 MySQL 本身就非常擅长。
没必要为了这种查询专门引入 ES。
十、删除 Document
DELETE /products/_doc/1001
十一、更新 Document
可以进行局部更新:
POST /products/_update/1001
{
"doc": {
"price": 7999
}
}
表示:
只修改 price
十二、match:全文搜索最常用查询
假设商品:
Apple iPhone 16 Pro
查询:
GET /products/_search
{
"query": {
"match": {
"name": "iPhone"
}
}
}
match 的特点是:
查询内容会经过分析器
因此适合:
全文搜索字段
通常对应:
text
类型。
记忆:
全文搜索
↓
match
十三、term:精确查询
假设:
brandName = Apple
查询:
GET /products/_search
{
"query": {
"term": {
"brandName": "Apple"
}
}
}
term 通常用于:
精确值匹配
适合:
ID
状态
品牌
类型
分类
SKU
通常搭配:
keyword
long
integer
boolean
等字段。
记忆:
全文搜索 → match
精确匹配 → term
十四、terms:匹配多个精确值
如果需要:
brandId = 1
或者
brandId = 2
或者
brandId = 3
可以:
GET /products/_search
{
"query": {
"terms": {
"brandId": [
1,
2,
3
]
}
}
}
可以理解为 SQL 中:
WHERE brand_id IN (1,2,3)
十五、range:范围查询
查询价格:
5000 ~ 10000
可以:
GET /products/_search
{
"query": {
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
}
}
其中:
gt = >
gte = >=
lt = <
lte = <=
范围查询特别常见于:
价格
时间
销量
年龄
评分
十六、bool:真实项目最重要的组合查询
真实业务搜索基本不可能只有一个条件。
例如用户搜索:
关键词:iphone
品牌:Apple
价格:5000~10000
状态:上架
这时候通常使用:
bool
例如:
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "iphone"
}
}
],
"filter": [
{
"term": {
"brandName": "Apple"
}
},
{
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
},
{
"term": {
"status": 1
}
}
]
}
}
}
十七、must、filter、should、must_not
Bool 查询里面有四个非常重要的结构。
must
表示:
必须满足
并且通常参与相关度评分。
例如:
"must": [
{
"match": {
"name": "iphone"
}
}
]
filter
同样表示:
必须满足
但是通常不参与相关度评分。
例如:
"filter": [
{
"term": {
"status": 1
}
}
]
在项目中:
status
brandId
categoryId
price
这种过滤条件通常放:
filter
而不是:
must
原因是这些条件通常不需要计算:
相关度
should
表示:
最好满足
可以用于提高某些结果的相关度。
例如:
"should": [
{
"term": {
"brandName": "Apple"
}
}
]
可以理解为:
满足更好
must_not
表示:
不能满足
例如:
"must_not": [
{
"term": {
"status": 0
}
}
]
表示排除:
下架商品
十八、multi_match:多个字段同时搜索
实际项目中,用户搜索关键词时通常不只搜索商品名称。
还可能搜索:
name
description
brand
category
这时候可以:
GET /products/_search
{
"query": {
"multi_match": {
"query": "iphone",
"fields": [
"name",
"description"
]
}
}
}
这样一个关键词就可以同时搜索多个字段。
十九、搜索字段加权
真实搜索系统通常认为:
商品名称命中
比:
商品描述命中
更加重要。
所以可以提高字段权重:
{
"multi_match": {
"query": "iphone",
"fields": [
"name^3",
"description"
]
}
}
其中:
name^3
表示提高 name 字段的重要程度。
这样:
商品名包含 iphone
的结果更容易排到前面。
二十、排序
按照价格升序:
GET /products/_search
{
"sort": [
{
"price": "asc"
}
]
}
按照销量降序:
{
"sort": [
{
"sales": "desc"
}
]
}
多字段排序:
{
"sort": [
{
"sales": "desc"
},
{
"price": "asc"
}
]
}
二十一、相关度排序
如果不指定业务排序,全文搜索通常会根据:
_score
进行相关性排序。
例如:
用户搜索:iphone
ES 会计算哪些商品和:
iphone
更加相关。
匹配程度更高的结果通常会排在前面。
这就是搜索引擎与普通数据库查询非常不同的地方。
数据库常常回答:
符合还是不符合?
搜索引擎还要回答:
哪个结果更符合?
二十二、分页
最基础分页使用:
from
size
例如:
GET /products/_search
{
"from": 0,
"size": 20,
"query": {
"match": {
"name": "iphone"
}
}
}
其中:
from = 从第几条开始
size = 返回多少条
第 1 页:
from = 0
size = 20
第 2 页:
from = 20
size = 20
Java 中通常:
from = (page - 1) * size;
二十三、为什么不能无限使用 from + size?
假设用户访问:
第 10000 页
每页:
20 条
那么:
from = 199980
size = 20
在分布式 Elasticsearch 中,各个分片需要产生大量候选数据,再由协调节点进行排序和合并。
所以:
from + size
不适合特别深的分页。
这种问题叫:
深度分页
二十四、search_after:深分页常用方案
对于深分页,可以考虑:
search_after
例如先排序:
{
"size": 20,
"sort": [
{
"sales": "desc"
},
{
"id": "asc"
}
]
}
上一页最后一条:
sales = 10000
id = 9527
下一页可以:
{
"size": 20,
"search_after": [
10000,
9527
],
"sort": [
{
"sales": "desc"
},
{
"id": "asc"
}
]
}
所以:
普通分页
↓
from + size
深分页 / 滚动式分页
↓
search_after
二十五、高亮
搜索引擎常见需求:
用户搜索:
苹果
页面显示:
最新 <em>苹果</em> 手机
可以使用:
GET /products/_search
{
"query": {
"match": {
"name": "苹果"
}
},
"highlight": {
"fields": {
"name": {}
}
}
}
ES 返回结果中会多:
"highlight": {
"name": [
"最新 <em>苹果</em> 手机"
]
}
前端就可以展示高亮。
二十六、_source 字段过滤
如果商品 Document 很大,但搜索结果只需要:
id
name
price
brandName
可以:
GET /products/_search
{
"_source": [
"id",
"name",
"price",
"brandName"
],
"query": {
"match": {
"name": "iphone"
}
}
}
这样可以减少:
返回数据量
网络传输
反序列化成本
二十七、聚合 Aggregation
Elasticsearch 不仅能搜索,也非常擅长统计。
例如搜索手机时,希望统计品牌:
Apple 2000
Huawei 1500
Xiaomi 1200
可以:
GET /products/_search
{
"size": 0,
"aggs": {
"brands": {
"terms": {
"field": "brandName"
}
}
}
}
返回类似:
{
"buckets": [
{
"key": "Apple",
"doc_count": 2000
},
{
"key": "Huawei",
"doc_count": 1500
}
]
}
二十八、为什么聚合经常使用 keyword?
假设:
brandName
是:
text
它可能会被分词。
聚合希望统计的是完整品牌:
Apple
Huawei
Xiaomi
因此通常使用:
keyword
如果字段 Mapping:
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
聚合时通常使用:
name.keyword
而不是:
name
二十九、平均值聚合
计算商品平均价格:
{
"size": 0,
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
三十、最大值、最小值、总和
最大值:
{
"aggs": {
"max_price": {
"max": {
"field": "price"
}
}
}
}
最小值:
{
"aggs": {
"min_price": {
"min": {
"field": "price"
}
}
}
}
总和:
{
"aggs": {
"total_sales": {
"sum": {
"field": "sales"
}
}
}
}
三十一、范围聚合
例如商品价格区间:
0~1000
1000~5000
5000~10000
10000+
可以:
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{
"to": 1000
},
{
"from": 1000,
"to": 5000
},
{
"from": 5000,
"to": 10000
},
{
"from": 10000
}
]
}
}
}
}
这种功能特别适合:
商城筛选
数据报表
日志统计
三十二、组合:搜索 + 筛选 + 聚合
真正项目里,经常不是:
只搜索
或者:
只聚合
而是:
搜索
+
过滤
+
聚合
例如:
搜索 iphone
只看上架商品
同时统计品牌
可以:
GET /products/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "iphone"
}
}
],
"filter": [
{
"term": {
"status": 1
}
}
]
}
},
"aggs": {
"brands": {
"terms": {
"field": "brandName"
}
}
}
}
这就是 Elasticsearch 在电商搜索中非常强大的地方。
三十三、中文分词
英文天然有空格:
Apple iPhone Pro
但是中文:
苹果手机保护壳
没有天然空格。
如果分词不好,搜索体验会非常差。
理想情况下可能希望拆成:
苹果
手机
保护壳
所以中文搜索项目通常会考虑中文分析器和中文分词方案。
例如项目中常见做法是安装合适的中文分析插件,然后在 Mapping 中指定 analyzer。
概念上可以理解:
"name": {
"type": "text",
"analyzer": "某个中文分析器"
}
真正生产环境需要结合:
业务词库
产品名称
品牌词
专业词
同义词
搜索日志
调整搜索效果。
三十四、分词效果查看
可以使用 Analyze API 测试:
POST /_analyze
{
"analyzer": "standard",
"text": "Apple iPhone 16 Pro"
}
它可以帮助开发者了解:
一段文本最终会被拆成哪些 token
这是分析:
为什么搜索不到
为什么匹配结果不符合预期
时非常重要的工具。
三十五、同义词
真实搜索系统经常需要解决:
苹果
Apple
iPhone
之间的关系。
例如用户搜索:
苹果手机
可能希望匹配:
Apple iPhone
这类需求通常要结合:
同义词
词库
业务规则
搜索权重
实现。
例如逻辑上:
苹果 => apple
手机 => phone
但是实际生产系统不能随便堆同义词。
因为同义词过多可能导致:
召回结果过多
相关度下降
搜索污染
三十六、Bulk 批量操作
如果要一次导入几十万商品,不应该:
循环 100000 次
每次请求 ES 一次
这样网络开销非常大。
Elasticsearch 提供:
Bulk API
例如:
POST /_bulk
{"index":{"_index":"products","_id":"1001"}}
{"id":1001,"name":"iPhone","price":8999}
{"index":{"_index":"products","_id":"1002"}}
{"id":1002,"name":"Huawei Mate","price":6999}
批量操作非常适合:
历史数据导入
索引重建
全量数据同步
批量更新
三十七、真实项目中 MySQL 数据怎么同步到 ES?
这是 ES 项目开发真正重要的地方。
假设管理员修改商品:
iPhone
8999
↓
7999
MySQL 更新:
UPDATE product
SET price = 7999
WHERE id = 1001;
但是 ES 中还是:
8999
就产生了数据不一致。
因此项目必须设计:
MySQL → ES
的数据同步机制。
三十八、最简单同步方式
最简单做法:
更新 MySQL
↓
更新 ES
伪代码:
updateMysql(product);
updateElasticsearch(product);
优点:
实现简单
缺点:
MySQL 成功
ES 失败
就会产生数据不一致。
而且 ES 响应速度还会影响业务接口。
所以生产环境通常需要更完善的方案。
三十九、MQ 异步同步
一种非常常见的架构:
商品服务
│
▼
MySQL
│
▼
发送 MQ
│
▼
Kafka / RabbitMQ / RocketMQ
│
▼
搜索服务
│
▼
Elasticsearch
例如商品修改后发送:
{
"type": "PRODUCT_UPDATE",
"productId": 1001
}
消费者收到:
productId = 1001
然后:
查询最新商品数据
↓
构造 ProductDocument
↓
更新 Elasticsearch
这样可以降低主业务与 ES 的直接耦合。
四十、为什么 ES 往往是最终一致性?
假设:
12:00:00.000
MySQL 修改成功
12:00:00.100
MQ 消息发送成功
12:00:00.300
消费者处理
12:00:00.500
ES 更新完成
这中间 ES 有短暂时间还是旧数据。
因此:
MySQL
↓
MQ
↓
ES
通常属于:
最终一致性
而不是:
强一致性
对于:
商品搜索
文章搜索
日志检索
通常可以接受。
但是:
支付
扣库存
余额
订单最终状态
不能只依赖 ES。
四十一、为什么 ES 不应该作为核心交易数据源?
例如搜索结果显示:
库存:10
但这可能是几百毫秒前的数据。
用户真正下单时,应该:
重新查询核心库存
所以:
搜索阶段
↓
Elasticsearch
真正交易阶段
↓
MySQL / Redis / 核心交易系统
这就是正确的职责边界。
四十二、全量同步
即使使用 MQ,也可能发生:
消息丢失
消费者异常
程序 Bug
ES 故障
历史数据遗漏
所以生产项目经常设计:
增量同步
+
全量同步
增量同步:
MySQL
↓
MQ
↓
ES
全量同步:
MySQL
↓
分页读取
↓
Bulk
↓
ES
因此,如果 ES 索引出了严重问题,可以:
删除旧索引
↓
重新建立 Mapping
↓
从 MySQL 全量重建
四十三、为什么 ES Document 经常和数据库实体不一样?
数据库可能有:
product
brand
category
product_attribute
四张表。
MySQL 通过:
JOIN
把它们关联起来。
但是 Elasticsearch 更常采用:
反范式设计
例如 ProductDocument:
{
"id": 1001,
"name": "iPhone 16 Pro",
"brandId": 1,
"brandName": "Apple",
"categoryId": 10,
"categoryName": "Phone",
"price": 8999
}
虽然:
brandName
categoryName
在数据库里可能存在其他表中,但是 ES 为了搜索方便,直接冗余进 Document。
所以:
数据库设计
关注:
数据规范
事务
数据一致性
而:
Elasticsearch Document
更加关注:
搜索效率
查询方式
搜索需求
四十四、别把 ES 当 MySQL 用
这是非常常见的错误。
很多开发者把 MySQL 表:
product
brand
category
原样建立成:
product_index
brand_index
category_index
然后希望像 SQL 一样各种 JOIN。
这种思维通常不适合 ES。
Elasticsearch 更推荐:
根据搜索场景设计 Document
而不是:
照搬数据库表结构
四十五、搜索系统的核心不是“能搜到”,而是“搜得准”
例如用户搜:
苹果
下面几个结果:
Apple iPhone
苹果手机壳
苹果水果
苹果味饮料
理论上都可能匹配。
但是一个电商手机商城里:
Apple iPhone
显然应该比:
苹果味饮料
更相关。
所以真正的搜索系统需要考虑:
召回率
准确率
相关度
字段权重
同义词
业务权重
用户行为
ES 提供的是搜索能力基础。
真正的:
搜索质量
仍然需要业务设计。
四十六、搜索结果业务加权
有时项目希望:
相关度高
+
销量高
+
评分高
的商品排在前面。
这时候不能只依赖:
_score
可能需要:
相关度
+
业务分数
进行综合排序。
这类问题可以进一步研究:
function_score
script_score
等功能。
四十七、日志搜索也是 ES 的经典场景
除了商品搜索,Elasticsearch 另一个非常经典的用途是:
日志检索
假设系统有:
user-service
order-service
product-service
payment-service
每天产生几千万条日志。
如果开发人员还需要:
SSH 到机器
grep 日志
会非常痛苦。
因此常见架构:
应用日志
↓
Agent / Filebeat
↓
数据处理
↓
Elasticsearch
↓
Kibana
开发人员可以搜索:
traceId = xxx
或者:
service = payment-service
level = ERROR
快速定位问题。
四十八、Elasticsearch 适合什么场景?
Elasticsearch 非常适合:
商品搜索
文章搜索
论坛搜索
知识库搜索
日志搜索
用户搜索
订单后台搜索
复杂条件筛选
全文检索
数据聚合分析
尤其是当业务要求:
分词
模糊匹配
相关度
多字段搜索
高亮
聚合
时,ES 的价值会非常明显。
四十九、什么时候不应该用 ES?
不要看到:
数据量大
就直接上 ES。
例如:
根据用户 ID 查询用户
MySQL:
SELECT *
FROM user
WHERE id = ?;
已经非常快。
又例如:
根据订单号查询订单
数据库建立索引以后同样很适合。
因此:
数据量大
并不是使用 Elasticsearch 的充分条件。
更重要的是:
搜索需求复杂
五十、MySQL、Redis、ES 到底怎么选?
可以先记住:
MySQL
↓
核心业务数据
事务
精确查询
Redis
↓
缓存
热点数据
高频读写
分布式能力
Elasticsearch
↓
搜索
全文检索
复杂筛选
聚合分析
日志检索
一句话:
MySQL 负责存准
Redis 负责读快
ES 负责搜好
当然真实系统会更加复杂,但这个理解非常适合建立基础架构认知。
五十一、项目中的典型搜索接口
假设前端请求:
GET /api/products/search
参数:
keyword=iphone
brandId=1
categoryId=10
minPrice=5000
maxPrice=10000
sort=sales
page=1
size=20
搜索条件可以设计为:
bool
├── must
│
└── match keyword
filter
├── brandId
├── categoryId
├── price
└── status
sort
└── sales desc
pagination
├── from
└── size
highlight
└── name
aggregation
├── brand
├── category
└── price range
这基本已经覆盖了大部分商城搜索业务。
五十二、一个较完整的查询示例
例如:
GET /products/_search
{
"_source": [
"id",
"name",
"brandName",
"price",
"sales"
],
"from": 0,
"size": 20,
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "iphone",
"fields": [
"name^3",
"description"
]
}
}
],
"filter": [
{
"term": {
"brandId": 1
}
},
{
"term": {
"categoryId": 10
}
},
{
"term": {
"status": 1
}
},
{
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
}
]
}
},
"sort": [
{
"sales": "desc"
}
],
"highlight": {
"fields": {
"name": {}
}
},
"aggs": {
"brands": {
"terms": {
"field": "brandName"
}
},
"categories": {
"terms": {
"field": "categoryName"
}
}
}
}
这一条 Query DSL 已经同时包含:
字段过滤
分页
全文搜索
多字段搜索
字段权重
精确筛选
范围筛选
排序
高亮
聚合
基本可以称为一个典型的项目级 Elasticsearch 查询。
五十三、Mapping 设计比你想象中重要
很多 Elasticsearch 性能问题和搜索问题,实际上不是 Query DSL 写错了。
而是:
Mapping 一开始就设计错了。
例如:
应该精确匹配的字段设计成:
text
应该全文搜索的字段设计成:
keyword
或者:
所有字段全部建立索引
最终都会带来问题。
所以设计 Index 前一定要问:
这个字段需要全文搜索吗?
需要过滤吗?
需要排序吗?
需要聚合吗?
需要存储吗?
需要分词吗?
然后再决定 Mapping。
五十四、不要什么字段都存进 ES
例如商品数据库有:
几十个内部管理字段
审核字段
财务字段
运营字段
备注字段
但是搜索只需要:
id
name
description
brand
category
price
sales
status
那就只建立真正需要用于:
搜索
筛选
排序
聚合
展示
的数据。
ES 索引并不是越大越好。
五十五、不要滥用 wildcard
有些开发者习惯把 SQL:
LIKE '%iphone%'
直接翻译成:
wildcard
然后大量使用。
这是危险的。
因为通配符查询尤其是前缀不固定的模式,可能带来比较高的执行成本。
如果需求本质是:
全文搜索
应该优先:
分析器
match
multi_match
而不是一上来就:
wildcard
五十六、避免超大分页
这种请求:
第 50000 页
对于搜索系统本身就不一定合理。
用户真的会翻到第 50000 页吗?
很多搜索产品会限制:
最大可访问结果范围
或者使用:
search_after
支持向后翻页。
五十七、Bulk 大小不是越大越好
Bulk 可以提高写入效率。
但如果一次:
几百万条 Document
全部塞进一个 Bulk 请求,也可能:
占用大量内存
请求过大
GC 压力
节点压力
超时
所以生产环境通常需要:
分批 Bulk
具体批次大小需要结合:
Document 大小
网络
集群配置
节点资源
写入吞吐
压测确定。
五十八、搜索慢首先检查什么?
当查询变慢时,不要第一时间就:
加机器
应该检查:
Mapping 是否合理
查询条件是否合理
是不是 wildcard 太多
是不是深分页
是不是返回字段太多
是不是聚合桶太多
是不是 shard 设计不合理
是不是节点资源不足
是不是写入压力太大
性能优化永远应该:
先定位瓶颈
再优化
五十九、Shard 是什么?
Elasticsearch 是分布式搜索引擎。
一个 Index 可以被拆成多个:
Shard
例如:
products
可能被分成:
Shard 0
Shard 1
Shard 2
数据分布在不同节点。
查询时,各个 Shard 可以分别执行搜索,再把结果进行合并。
这是 Elasticsearch:
水平扩展
的重要基础。
六十、Replica 是什么?
Replica 就是副本。
例如:
Primary Shard
有对应的:
Replica Shard
副本可以提高:
高可用性
读取能力
如果一个节点故障,副本可以帮助保证数据和查询能力。
六十一、为什么 Shard 不是越多越好?
很多初学者会觉得:
Shard 越多
并行度越高
性能越好
实际上错误。
Shard 本身也有:
内存成本
文件句柄
管理成本
查询协调成本
如果只有少量数据,却创建大量 Shard:
100 MB 数据
100 个 Shard
往往反而更加低效。
所以 Shard 数量需要结合:
索引体量
节点数量
查询压力
写入压力
未来增长
设计。
六十二、ES 为什么是 Near Real Time?
Elasticsearch 经常被称为:
近实时搜索
因为数据写进去后,不一定在同一个瞬间立刻对搜索可见。
这是因为 Elasticsearch 会经过类似:
写入
↓
buffer
↓
refresh
↓
searchable
的过程。
所以项目中不要错误地假设:
ES 写成功
=
下一微秒所有查询一定立刻看到
对于搜索业务来说,通常这种近实时特性完全可以接受。
六十三、ES 不适合强事务场景
Elasticsearch 不是为:
银行转账
支付事务
库存严格扣减
账户余额
这类强事务业务设计的。
这些场景应该使用:
关系型数据库
事务系统
ES 更适合:
搜索型数据
分析型数据
日志型数据
六十四、Spring Boot 项目中的对象设计
例如数据库实体:
public class Product {
private Long id;
private String name;
private Long brandId;
private Long categoryId;
private BigDecimal price;
private Integer stock;
}
而 ES Document:
public class ProductDocument {
private Long id;
private String name;
private Long brandId;
private String brandName;
private Long categoryId;
private String categoryName;
private BigDecimal price;
private Integer sales;
}
可以看到:
Product
和:
ProductDocument
并不一定完全一样。
因为:
Product
服务于:
业务数据库
而:
ProductDocument
服务于:
搜索
六十五、真实项目的数据流
商品修改:
管理员
↓
ProductController
↓
ProductService
↓
MySQL
↓
发送 MQ
↓
Search Consumer
↓
Elasticsearch
用户搜索:
用户
↓
SearchController
↓
SearchService
↓
Elasticsearch
↓
搜索结果
↓
前端
这就是非常典型的 Elasticsearch 项目架构。
六十六、一个 ES 项目的完整知识体系
真正掌握 Elasticsearch,可以按照下面顺序学习:
第一阶段
Index
Document
Field
Mapping
text
keyword
第二阶段
match
term
terms
range
bool
第三阶段
must
filter
should
must_not
第四阶段
multi_match
排序
分页
高亮
第五阶段
Aggregation
terms
avg
sum
min
max
range
第六阶段
Analyzer
中文分词
同义词
字段权重
相关度
第七阶段
Bulk
全量同步
增量同步
MQ
第八阶段
Shard
Replica
Refresh
深分页
第九阶段
搜索性能优化
Mapping 优化
索引设计
数据一致性
学到这里,已经可以覆盖大部分普通业务项目中 Elasticsearch 的使用场景。
六十七、ES 最常见面试问题
实际面试中,经常会被问:
为什么使用 Elasticsearch?
ES 和 MySQL 有什么区别?
什么是倒排索引?
text 和 keyword 有什么区别?
match 和 term 有什么区别?
must 和 filter 有什么区别?
ES 如何实现分页?
深分页怎么解决?
ES 数据和 MySQL 如何同步?
为什么用 MQ 同步?
ES 数据不一致怎么办?
什么是 Shard?
什么是 Replica?
ES 为什么搜索快?
Elasticsearch 为什么不是强实时?
你们项目 ES 用在哪?
如果真正理解前面的内容,这些问题基本都可以回答。
六十八、项目中什么时候应该使用 ES?
最后可以使用这个判断标准。
如果需求只是:
根据 ID 查数据
使用:
MySQL
如果需求是:
事务
使用:
MySQL
如果需求是:
缓存热点数据
使用:
Redis
如果需求开始出现:
关键词搜索
全文检索
分词
模糊搜索
多字段搜索
相关度排序
复杂筛选
高亮
聚合分析
海量日志检索
那么:
Elasticsearch
就开始真正发挥价值。
总结
Elasticsearch 最重要的能力可以概括成:
全文检索
+
倒排索引
+
分词
+
相关度
+
复杂过滤
+
排序
+
分页
+
高亮
+
聚合
+
分布式
实际项目中的典型架构是:
MySQL
主数据来源
│
│
MQ / 数据同步
│
▼
Elasticsearch
搜索索引
│
▼
用户搜索
一定要记住:
Elasticsearch 的最大价值不是“代替数据库”,而是把复杂搜索这件事情做好。
所以真正学习 ES,不应该只会背:
match
term
bool
range
而应该建立完整思维:
为什么需要搜索引擎
↓
数据怎么设计
↓
Mapping 怎么设计
↓
数据怎么进入 ES
↓
用户怎么搜索
↓
查询怎么组合
↓
结果怎么排序
↓
怎么高亮
↓
怎么聚合
↓
怎么分页
↓
怎么保证 MySQL 和 ES 最终一致
↓
怎么处理性能问题
当这整条链路真正串起来之后,Elasticsearch 才算真正从“会写几个 DSL”升级成“会在项目里使用 Elasticsearch”。