Elasticsearch 第一单元
一、ES 基础认知(理论)
ES 是分布式全文检索引擎,底层基于 Lucene,用 Java 开发,主打海量文本检索、日志分析、多维聚合,常用于 ELK/ELFK、商品搜索、日志平台。
| Elasticsearch | 关系型数据库 MySQL | 说明 |
|---|---|---|
| Index(索引) | Database 数据库 | 存放一类数据,如 order_log、goods |
| Type(类型) | Table 表 | ES7.0 后彻底废弃,一个索引只能一种数据 |
| Document(文档) | Row 行数据 | 单条数据,JSON 格式,ES 最小存储单元 |
| Field(字段) | Column 列 | 文档内每个键值对,如 name、age、content |
| Mapping(映射) | Schema 表结构 | 定义字段类型、分词、索引规则 |
| Shard(分片) | 分库分表 | 拆分大索引,实现水平扩容,分主分片 + 副本分片 |
| Query DSL | SQL | ES 专用 JSON 查询语法 |
二、核心概念详解
1.集群基础操作
实操 1:查看集群健康状态
GET _cluster/health
返回关键字段:
status:green/yellow/rednumber_of_nodes:集群节点总数active_shards:正常分片数
状态解释:
- green:主、副本分片全部正常,集群健康
- yellow:主分片全部正常,副本未分配(单机单节点必黄,数据不丢)
- red:存在主分片丢失,索引损坏、数据丢失
实操 2:查看集群所有节点信息
GET _cat/nodes?v
v 代表 verbose,打印表头;可看到节点角色、内存、CPU、磁盘占用
实操 3:查看集群分片分配详情
GET _cat/shards?v
2.Index 索引(库)
实操 1:创建索引(指定主分片、副本数量)
创建索引 goods,3 个主分片,1 个副本:
PUT goods
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
}
}
关键规则:
number_of_shards主分片:索引创建后无法修改number_of_replicas副本:可随时动态调整
实操 2:查看单个索引配置
GET goods/_settings
实操 3:修改副本数量(动态调整)
PUT goods/_settings
{
"number_of_replicas": 0
}
实操 4:查看集群全部索引
GET _cat/indices?v
实操 5:删除索引(危险操作)
DELETE goods
3 Document 文档(行数据,JSON)
内置系统字段:
_id:文档唯一标识,自定义 / 自动生成_index:归属索引_source:原始完整 JSON 数据_score:全文检索匹配分值
实操 1:新增文档(指定自定义_id=1)
PUT goods/_doc/1
{
"name": "华为Mate60手机",
"price": 5999,
"tag": ["手机","数码"],
"create_time": "2026-07-19"
}
实操 2:新增文档,ES 自动生成_id
POST goods/_doc
{
"name": "小米14",
"price": 3999,
"tag": ["手机","性价比"]
}
实操 3:根据 ID 查询单条文档
GET goods/_doc/1
实操 4:全量修改文档(覆盖整条数据)
PUT goods/_doc/1
{
"name": "华为Mate60 Pro",
"price": 6499,
"tag": ["手机","旗舰"],
"create_time": "2026-07-19"
}
实操 5:局部更新文档(只改部分字段)
POST goods/_update/1
{
"doc": {
"price": 6299
}
}
实操 6:删除文档
DELETE goods/_doc/1
4 Mapping 映射(表结构,定义字段类型)
两大模式:
- 动态 mapping:写入数据 ES 自动推断字段类型,生产不推荐,容易类型错乱
- 静态 mapping:创建索引时手动定义,企业标准用法
常用字段类型
- text:可分词,用于全文检索(商品名称、日志内容)
- keyword:不分词,精确匹配、聚合、排序(标签、手机号)
- long/integer/double:数值
- date:日期
- boolean:布尔
- object:普通嵌套对象
- nested:对象数组专用
实操 1:创建索引同时自定义静态 Mapping
PUT product
{
"settings": {
"number_of_shards": 2,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"title": {
"type": "text"
},
"brand": {
"type": "keyword"
},
"price": {
"type": "integer"
},
"publish_time": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
实操 2:查看索引 Mapping 结构
GET product/_mapping
实操 3:给已有索引新增字段(不能修改原有字段类型)
PUT product/_mapping
{
"properties": {
"stock": {
"type": "integer"
}
}
}
三、底层核心原理(理论)
1 .正向索引(数据库存储)
文档 ID → 完整文本
查询方式:遍历全部文档逐行匹配,海量数据性能极差。
2 .倒排索引(ES 检索极速的核心)
结构:分词词条 → 文档 ID 列表
示例文档:
doc1:华为 Mate60 旗舰手机
doc2:小米性价比手机
分词结果:华为、Mate60、旗舰、手机、小米、性价比
倒排链表:
手机:doc1, doc2
华为:doc1
小米:doc2 查询关键词直接匹配词条,不用遍历全量数据,毫秒级返回。
3 Segment 段、Translog 事务日志
- Segment:磁盘最小存储单元,新写入数据生成独立小段,后台自动 merge 合并;大量小段会消耗 CPU、IO。
- Translog:写入时同步记录事务日志,服务器宕机后依靠 translog 恢复未落地磁盘的数据。
- refresh(默认 1s):缓冲区数据生成新 segment,数据可被检索;频繁刷新产生大量小段。
- flush:将 segment 持久化磁盘,清空 translog。
四、集群节点与分片架构(理论)
1 节点 5 种角色
- master 主节点:管理集群元数据(索引、分片分配),同一集群仅 1 个活跃 master
- data 数据节点:存储分片,执行读写、聚合计算,业务核心
- coordinating 协调节点:接收客户端请求,分发分片任务、合并结果,所有节点默认拥有
- ingest 节点:写入前预处理文档清洗数据
- 冷热分层节点:hot 热写入、warm 温只读、cold 冷归档
2 分片 Shard
- 主分片 primary:创建索引指定,不可修改;文档通过
hash(_id) % 主分片数路由到对应分片;所有写入先写主分片。 - 副本分片 replica:主分片备份,不能和主分片同节点;提升高可用、分担读压力;数量可动态调整。
3 故障转移机制
节点宕机 → 该节点分片离线 → 副本自动升级为新主分片 → 集群自动创建新副本维持配置副本数量。
Elasticsearch 第二单元:分词器、DSL 基础查询、文档写入底层流程
一、分词器 Analyzer(全文检索核心)
1. 分词器三组件完整执行流程
一段文本处理分三步,顺序固定:
- Character Filter 字符过滤器 清洗原始文本:去除 html 标签、替换特殊符号、替换敏感词,可配置多个。 例:
<p>手机!</p>→手机! - Tokenizer 分词器(切词核心) 按规则把文本切割成一个个词条 token。 标准分词、空格分词、IK 中文分词都属于 Tokenizer。
- Token Filter 词条过滤器 对切好的词条二次处理:小写转换、停用词删除、同义词扩展、长度过滤。
2. ES 内置标准分词器 standard(默认)
特点:英文友好,中文会单字拆分,不适合中文业务。 测试分词 API:_analyze 调试工具
GET _analyze
{
"analyzer": "standard",
"text": "华为Mate60 旗舰智能手机"
}
输出效果:华、为、Mate60、旗、舰、智、能、手、机(中文单字拆分,无法完整匹配短语)
3. 中文必备:IK 分词器(第三方插件)
两种模式:
ik_smart:粗粒度分词,短句合并,适合索引存储ik_max_word:细粒度全切分,拆分所有组合词,适合搜索查询
IK 分词实操测试
GET _analyze
{
"analyzer": "ik_max_word",
"text": "华为Mate60 旗舰智能手机"
}
输出词条:华为、mate60、旗舰、智能、手机、智能手机
GET _analyze
{
"analyzer": "ik_smart",
"text": "华为Mate60 旗舰智能手机"
}
输出词条:华为、mate60、旗舰智能手机
4. 自定义分词器(业务实战)
创建索引时自定义 analyzer,组合字符过滤、分词器、过滤:
PUT goods
{
"settings": {
"analysis": {
"analyzer": {
"my_ik_analyzer": {
"tokenizer": "ik_max_word",
"filter": ["lowercase"]
}
}
}
},
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "my_ik_analyzer"
},
"brand": {
"type": "keyword"
}
}
}
}
5. 字段分词规则区分
text字段:存入时使用 mapping 指定分词器生成倒排索引,查询时同样分词匹配,用于模糊搜索;keyword字段:不分词,完整原文作为单个词条,只能精确匹配,用于筛选、分组、排序。
二、Query DSL 基础查询语法
前置:查询两大分类
- 全文检索(match 系列):只作用于 text 字段,查询关键词会先分词再匹配,有相关性_score 分值
- 精确检索(term/terms/range):作用于 keyword、数值、日期,不分词完全匹配,无分值,适合 filter 过滤
2.1 匹配所有文档 match_all
GET goods/_search
{
"query": {
"match_all": {}
}
}
分页控制 size(默认 10 条)
GET goods/_search
{
"query": {
"match_all": {}
},
"size": 20
}
2.2 全文检索 match(最常用)
text 字段分词模糊匹配
GET goods/_search
{
"query": {
"match": {
"title": "华为手机"
}
}
}
逻辑:"华为手机" 分词为华为、手机,文档包含任意一个词条都会返回,匹配越多分数越高。
2.3 短语匹配 match_phrase(词条必须连续)
要求关键词分词后在原文连续出现,精准短语搜索
GET goods/_search
{
"query": {
"match_phrase": {
"title": "华为手机"
}
}
}
2.4 多字段检索 multi_match
同时在多个 text 字段搜索关键词
GET goods/_search
{
"query": {
"multi_match": {
"query": "华为",
"fields": ["title","desc"]
}
}
}
2.5 精确查询 term /terms(keyword 专用)
term:单个值精确匹配
GET goods/_search
{
"query": {
"term": {
"brand": {
"value": "华为"
}
}
}
}
terms:多值匹配,等价 SQL in
GET goods/_search
{
"query": {
"terms": {
"brand": ["华为","小米"]
}
}
}
重要坑:不要用 term 查询 text 字段!text 存入是分词后的碎片,term 搜完整字符串匹配不到。
2.6 区间查询 range(价格、时间)
gte 大于等于,lte 小于等于,gt 大于,lt 小于
GET goods/_search
{
"query": {
"range": {
"price": {
"gte": 3000,
"lte": 7000
}
}
}
}
时间区间示例:
GET goods/_search
{
"query": {
"range": {
"create_time": {
"gte": "2026-01-01",
"lte": "2026-07-19"
}
}
}
}
2.7 组合查询 bool(业务必用核心)
bool 包含 4 种子句:
must:必须匹配,会计算相关性分值(AND)filter:必须匹配,不计算分值、自带缓存,性能更高,过滤条件优先放 filtershould:可选匹配,满足越多分数越高(OR)must_not:一定不能匹配,无分值
示例:华为手机,价格 3000~8000,排除苹果
GET goods/_search
{
"query": {
"bool": {
"must": [
{"match": {"title": "手机"}}
],
"filter": [
{"term": {"brand": "华为"}},
{"range": {"price": {"gte":3000,"lte":8000}}}
],
"must_not": [
{"term": {"brand": "苹果"}}
]
}
}
}
2.8 排序 sort
keyword、数值、日期字段支持排序;text 字段不能直接排序
GET goods/_search
{
"query": {
"match_all": {}
},
"sort": [
{"price": "desc"},
{"create_time": "asc"}
]
}
三、文档写入、更新、删除底层完整流程
3.1 写入完整流程
-
请求发送至协调节点,通过
hash(_id) % 主分片数路由到对应主分片节点 -
主分片接收数据:
- 写入内存缓冲区(内存段)
- 同步写入 translog 事务日志(持久化磁盘,宕机恢复数据)
-
主分片同步数据到所有副本分片,全部副本写入成功后,返回客户端写入成功
-
refresh(默认 1 秒) 缓冲区数据生成独立 Segment 小段,写入内存,数据变为可检索;频繁手动 refresh 会生成大量小段,加重合并压力。 手动刷新 API:
POST goods/_refresh -
flush(后台定时执行) 将内存 Segment 持久化到磁盘,清空 translog,落地后就算断电数据不丢失。 手动刷盘:
POST goods/_flush -
Segment Merge 段合并 后台线程自动将多个小 Segment 合并为大 Segment,同时清理标记删除的文档;合并消耗 CPU/IO,是集群常见性能瓶颈。
3.2 更新底层原理(无原地修改es故意设置)
ES 底层不支持修改已有 Segment 数据,更新逻辑:
- 根据_id 查询旧文档,给旧文档打上「删除标记」,查询时自动过滤标记删除数据
- 写入一条全新完整文档覆盖
- 旧文档不会立刻删除,仅段合并时永久清除 大量频繁更新会产生海量小段与删除标记,性能大幅下降。
3.3 删除底层原理
- 对应文档添加删除标记,不会立刻从磁盘清除
- 仅在 Segment 合并阶段,彻底移除带删除标记的数据释放磁盘空间
3.4 translog 作用
- 缓冲阶段数据仅在内存,断电丢失风险,translog 实时落盘兜底;
- flush 成功后 translog 清空,不需要重复恢复;
- 集群重启时读取 translog 恢复未 flush 的数据。
Elasticsearch 第三单元:聚合 Aggs、分页问题、集群运维、经典三大问题、生产最佳实践
一、聚合 Aggs(多维统计,ES 核心优势)
聚合分为两大类:桶聚合 Bucket(分组) + 指标聚合 Metric(计算数值)
1. 指标聚合 Metric(单字段统计,无分组)
用于计算最大 / 最小 / 平均 / 总和、去重数量
- avg:平均值
- sum:求和
- max/min:最大最小值
- count:文档总数
- cardinality:基数(去重计数,类似 distinct)
实操:商品价格统计
GET product/_search
{
"size": 0, // 不需要返回原始文档,只看聚合结果
"aggs": {
"price_stats": { // 自定义聚合名称
"stats": { // 一次性返回avg/sum/max/min/count
"field": "price"
}
},
"brand_distinct": {
"cardinality": {
"field": "brand"
}
}
}
}
2. 桶聚合 Bucket(分组,类似 group by)
(1)terms 普通分组(按关键词字段分组)
统计每个品牌商品数量、均价
GET product/_search
{
"size": 0,
"aggs": {
"group_by_brand": {
"terms": {
"field": "brand",
"size": 10 // 返回前10个分组
},
"aggs": { // 分组内嵌套指标聚合
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
(2)date_histogram 时间直方图(日志最常用,按时间分组)
按天统计商品上架数量,interval 可选:hour/day/month/year
GET product/_search
{
"size": 0,
"aggs": {
"group_by_day": {
"date_histogram": {
"field": "online_time",
"calendar_interval": "day",
"format": "yyyy-MM-dd"
}
}
}
}
(3)range 区间分组
按价格区间分组:0-3000、3000-6000、6000 以上
GET product/_search
{
"size": 0,
"aggs": {
"price_range_group": {
"range": {
"field": "price",
"ranges": [
{"to": 3000},
{"from": 3000, "to": 6000},
{"from": 6000}
]
}
}
}
}
3. 聚合过滤搭配查询
先筛选华为商品,再做聚合统计
GET product/_search
{
"size": 0,
"query": {
"term": {
"brand": "华为"
}
},
"aggs": {
"huawei_price_avg": {
"avg": {"field": "price"}
}
}
}
二、分页方案与深度分页性能灾难
1. from + size(前端普通分页,有上限缺陷)
语法示例:查询第 100 页,每页 10 条
GET product/_search
{
"from": 990,
"size": 10,
"query": {
"match_all": {}
}
}
致命问题
ES 需要将所有分片前 10000 条数据全部加载到内存排序,页数越大内存消耗越高,默认 index.max_result_window:10000,超过直接报错。
适用场景:只允许前几十页,不支持无限下拉翻页
2. scroll 游标分页(海量数据导出,不适合前端翻页)
原理:生成快照,保存排序上下文,持续滚动拉取全量数据
实操
-
初始化 scroll,有效期 5 分钟
GET product/_search?scroll=5m
{
"size": 100,
"query": {"match_all": {}}
}
返回结果中携带 _scroll_id 2. 使用 scroll_id 循环拉取下一批
GET _search/scroll
{
"scroll": "5m",
"scroll_id": "返回的scroll_id值"
}
缺点
无法实时获取新增数据,占用集群内存,不适合前端分页,仅用于离线导出数据
3. search_after(推荐!大数据下拉分页,无深度限制)(类似于游标,惰性求值)
基于上一页最后一条文档的排序值向后滚动,无 10000 条上限
使用前提:必须指定唯一稳定排序字段(如 _id)
// 第一页
GET product/_search
{
"size": 10,
"sort": [
{"price": "desc"},
{"_id": "asc"} // 唯一兜底排序
]
}
拿到最后一条文档 sort 数组 [5999, "1"]
// 下一页
GET product/_search
{
"size": 10,
"search_after": [5999, "1"],
"sort": [
{"price": "desc"},
{"_id": "asc"}
]
}
优点:性能稳定,无深度分页限制,前端下拉加载首选
三、线上三大经典问题(缓存穿透、击穿、雪崩)
ES 常配合 Redis 缓存使用,三类问题通用
1. 缓存击穿
现象:某热点数据缓存过期,大量并发请求同时直达 ES,压垮集群 解决方案:
- 热点 key 设置永不过期
- 分布式互斥锁,同一时间只放行一个请求查询 ES
2. 缓存雪崩
现象:大量缓存 key 同一时刻过期,流量全部打入 ES 解决方案:
- 过期时间增加随机偏移值,打散过期时间
- 分层设置过期时间,避免集中失效
3. 缓存穿透
现象:查询不存在的数据,缓存无记录,每次都查询 ES(如非法 id) 解决方案:
- 布隆过滤器拦截不存在 id
- 查询空结果时,缓存空值,设置短过期时间
四、集群运维与生产实操命令
1. ILM 索引生命周期管理(ELK 日志必备)
自动完成:冷热迁移、收缩分片、删除过期日志 四阶段:
- hot:热阶段,写入查询
- warm:温阶段,只读,压缩分片
- cold:冷阶段,归档,降低硬件成本
- delete:删除过期索引
2. 快照 snapshot 索引备份恢复
-
创建快照仓库(存储备份文件路径)
-
对索引打快照全量备份
-
集群故障时从快照恢复索引数据 实操命令(创建快照)
PUT /_snapshot/backup_repo/snapshot_20260719
{
"indices": "product",
"ignore_unavailable": true,
"include_global_state": false
}
3. 冷热数据分层节点
节点配置标记 node.attr.data_type: hot/warm/cold,配合 ILM 自动迁移分片,节省服务器成本。
4. 分片生产最佳规范
- 单个分片数据控制在 10G ~ 30G
- 主分片数 = 数据节点数量倍数,均衡分片分配
- 生产副本数至少 1,单机测试副本 0
- 分片过多:大量 segment,merge 消耗 CPU;分片过少:单分片数据过大查询慢
5. 批量写入 _bulk 高性能 API
单请求批量增删改,大幅提升写入吞吐量 语法格式:操作元数据行 + 数据行,换行分隔
POST product/_bulk
{"index":{"_id":4}}
{"title":"OPPO手机","brand":"OPPO","price":3499,"online_time":"2026-04-01"}
{"update":{"_id":3}}
{"doc":{"price":2299}}
{"delete":{"_id":2}}
规范:单批数据控制 500~1000 条,单批大小不超过 15MB
五、进阶高频知识点(面试必考)
1. BM25 打分算法
ES5.0 之后默认,替代老旧 TF-IDF 算法;解决 TF-IDF 长文档分值虚高问题,更贴合真实搜索场景,控制词条重复堆砌权重。
2. nested 嵌套对象
普通 object 数组会扁平化,导致关联查询错乱;nested 类型将数组对象独立存储,支持对象内部精准过滤、聚合。 创建 mapping 示例:
PUT shop
{
"mappings": {
"properties": {
"shop_name": {"type":"keyword"},
"goods_list": {
"type": "nested",
"properties": {
"goods_name": {"type":"text"},
"price": {"type":"integer"}
}
}
}
}
}
查询时必须使用 nested 查询语法。
3. 路由 routing 自定义分片分配
文档默认用 _id 哈希分片;自定义 routing 字段可将关联数据存入同一分片,跨分片聚合性能大幅提升。 写入指定 routing:
PUT product/_doc/1?routing=brand_huawei
{
"title":"华为手机","brand":"华为"
}
查询时必须携带相同 routing,否则查不到数据。