微服务学习最终篇:Elasticsearch 从入门到生产实战
Elasticsearch(简称 ES)是一个高性能的分布式搜索引擎。它基于 Lucene 构建,支持分布式、可水平扩展,并提供 Restful 接口,可被任何编程语言调用。ELK 技术栈(Elasticsearch + Logstash + Kibana + Beats)已成为日志分析、实时监控领域的行业标配。
全文目录:
- 倒排索引 ------ ES 为什么这么快
- 核心概念:索引、文档、映射
- IK 分词器 ------ 中文搜索的救星
- 索引库与文档操作
- RestClient 系列 ------ Java 客户端怎么选
- 聚合分析 ------ 为什么需要数据聚合
- 分布式架构与集群高可用
- 读写流程原理
- 生产环境性能优化(加分项)
- 面试高频题汇总
一、倒排索引 ------ ES 为什么这么快
1.1 什么是倒排索引
Elasticsearch 之所以能在大数据量下实现毫秒级的全文搜索,核心秘密就在于它的底层数据结构------倒排索引(Inverted Index) 。
在传统关系型数据库(如 MySQL)中,搜索文本字段通常用 LIKE %keyword%,需要扫描每一行记录,效率极低。倒排索引的设计思路完全不同------它不存储"哪个文档包含了什么内容",而是存储 "这个关键词出现在了哪些文档中" 。
正排索引 vs 倒排索引:
- 正排索引(Forward Index) :以文档为核心,文档 ID → 文档内容。这是存储原始数据的方式。
- 倒排索引(Inverted Index) :以词(Term)为核心,关键词 → 包含该词的文档 ID 列表。这是用来搜索的方式。
用书籍来类比:
- 正排索引就像书的目录(章节 → 页码),你知道第一章讲什么,但不知道"并发"这个词出现在哪几页。
- 倒排索引就像书尾的索引页(关键词 → 页码列表),你可以直接找到"并发"出现在第 15、28、99 页。
1.2 核心结构
倒排索引由两部分组成:
| 组成部分 | 说明 |
|---|---|
| 词项字典(Term Dictionary) | 所有不重复词的有序列表 |
| 倒排列表(Posting List) | 每个词对应的文档 ID 列表(还包含位置、频率等信息) |
举例:假设有三条商品数据:
- Doc 1:红富士苹果
- Doc 2:新鲜的香蕉
- Doc 3:苹果和香蕉
构建倒排索引后:
| Term(词项) | Posting List(文档 ID 列表) |
|---|---|
| 苹果 | 1, 3 |
| 红富士 | 1 |
| 香蕉 | 2, 3 |
| 新鲜 | 2 |
当搜索"苹果"时,ES 直接定位到倒排索引中的"苹果"一行,立即得到文档 ID 1, 3,无需遍历所有数据。
1.3 倒排索引的构建流程
倒排索引的构建需要经过分词(Tokenization) 和归一化(Normalization) 等处理:
- 字符过滤:去除 HTML 标签等无关字符
- 分词(Tokenization) :将文本拆分为词语,如
"Hello World" → ["Hello", "World"] - 归一化 :转小写(
FUN → fun)、去除停用词(is、the)、词干提取(running → run)
Elasticsearch 默认使用 standard 分析器,也支持自定义分析器(如 IK 中文分词器)。
1.4 Lucene 底层存储
每个 ES 分片底层对应一个 Lucene 索引实例。倒排索引在 Lucene 中由多个文件组成:
.tim文件:存储词典(Term Dictionary),使用 FST 压缩存储,支持高效前缀查询.doc文件:存储 Posting List(文档 ID、词频等).pos文件:存储词项在文档中的位置(用于短语查询).pay文件:存储额外负载信息
二、核心概念:索引、文档、映射
2.1 索引(Index)
索引是存储相关数据的逻辑容器,类似于关系型数据库中的"数据库"或"表"。它是一组具有相似特征的文档集合。
⚠️ 注意:ES 中"索引"一词有双重含义:
- 名词 :数据的逻辑存储单元(如
order_index)- 动词:将文档写入 ES 的过程(如 "index a document")
ES 存储采用 JSON 格式,与 MongoDB 类似。
2.2 文档(Document)
文档是 ES 中可被索引的基本数据单元,采用 JSON 格式表示。每个文档属于一个索引,包含多个字段(Field)。
文档是无模式(schema-less) 的,但生产环境通常通过映射来定义结构以保证一致性。
2.3 映射(Mapping)------ 面试必考
映射是定义索引中文档结构的元数据,相当于数据库中的"表结构" 。它规定了每个字段的类型(如 text、keyword、date)、是否分词、是否可被搜索等属性。
🔥 面试高频题:text 和 keyword 有什么区别?
这是几乎所有 ES 面试必问的一道题。
| 维度 | text | keyword |
|---|---|---|
| 是否分词 | ✅ 是 | ❌ 否 |
| 支持全文检索 | ✅ match 查询 |
❌ 只能 term 精确匹配 |
| 支持聚合 | ❌(需开启 fielddata,易 OOM) | ✅ 原生支持 |
| 支持排序 | ❌(同上) | ✅ |
| 存储开销 | 高(倒排索引 + norms) | 低 |
生产建议:
- 需要全文搜索 的字段 →
text(如文章内容、商品描述) - 需要精确匹配、聚合、排序 的字段 →
keyword(如状态、标签、用户 ID)
⚠️ 生产警告:不要依赖动态映射
ES 默认支持动态映射(Dynamic Mapping) :插入新字段时自动推断类型。
但这份"智能"会埋下隐患:
user_id一开始是数字(如"12345"),被识别为long- 后来第三方传来 UUID(如
"abc-def"),直接写入失败------类型冲突!
生产环境原则:永远不要依赖自动映射,必须显式定义 Mapping。
三、IK 分词器 ------ 中文搜索的救星
3.1 为什么需要 IK 分词器
ES 默认的分词器(如 Standard Analyzer)对中文支持较差,会将中文词语拆分为单个汉字(如"华为手机"拆分为"华""为""手""机"),无法满足中文全文检索需求。
IK 分词器是专门为中文设计的分词器,支持自定义词典,能准确拆分中文词语。
3.2 安装与使用
安装(版本必须与 ES 版本完全一致):
bash
# 进入 ES 插件目录,下载对应版本的 IK 分词器
# https://github.com/medcl/elasticsearch-analysis-ik/releases
# 解压到 plugins/ik 目录,重启 ES
两种分词模式:
ik_max_word:最大粒度分词,将文本拆分为尽可能多的词语(索引时用)ik_smart:最小粒度分词,将文本拆分为最合理的词语(搜索时用)
测试分词:
json
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "华为Mate 60 Pro手机"
}
3.3 自定义词典(生产必备)
编辑 plugins/ik/config/custom.dic 文件,添加自定义词语(如品牌名、专业术语),IK 分词器会优先识别这些词。
四、索引库与文档操作
4.1 索引库 CRUD
创建索引并定义 Mapping:
json
PUT /product_index
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"title": { "type": "text", "analyzer": "ik_max_word" },
"brand": { "type": "keyword" },
"price": { "type": "float" },
"created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }
}
}
}
常用 Mapping 属性:
type:字段类型(text、keyword、long、date 等)analyzer:指定分词器index:是否可被搜索(true/false)doc_values:是否支持聚合排序(默认开启,text 类型默认关闭)
4.2 文档 CRUD
- 新增/覆盖 :
POST /index/_doc/id或PUT /index/_doc/id - 查询 :
GET /index/_doc/id - 更新 :
POST /index/_update/id(局部更新) - 删除 :
DELETE /index/_doc/id - 批量操作 :
POST /_bulk
五、RestClient 系列 ------ Java 客户端怎么选
5.1 三个客户端的演进
| 客户端 | 状态 | 说明 |
|---|---|---|
| TransportClient | ❌ 已废弃 | 早期客户端,使用 TCP 协议 |
| RestClient(低级) | ✅ 可用 | 纯 HTTP 通信,无 ES API 封装 |
| RestHighLevelClient | ⚠️ 7.15 弃用,8.0 移除 | 基于 RestClient 封装,提供 ES API |
| Java API Client(新) | ✅ 官方推荐 | 全新设计,零 Server 依赖 |
5.2 RestClient vs RestHighLevelClient
| 对比维度 | RestClient(低级) | RestHighLevelClient(高级)【已弃用】 |
|---|---|---|
| 封装程度 | 纯 HTTP 工具 | 完整 ES API 封装 |
| 请求方式 | 手动拼接 JSON、URL | 链式 API,自动构建 |
| 版本要求 | 宽松,跨版本兼容 | 必须与 ES 版本一致 |
| 代码量 | 大,重复代码多 | 简洁,易维护 |
| 适用场景 | 特殊接口、调试 | 曾是 90% 业务首选 |
5.3 新项目怎么选?
直接使用 Elasticsearch Java API Client(官方推荐)。它:
- 与 ES Server 代码零耦合,纯 REST 协议
- API 更一致、更易用
- 是未来长期维护的方向
迁移时需要重写代码(非兼容升级)。
六、聚合分析 ------ 为什么需要数据聚合
6.1 什么是聚合?
如果说查询是"找数据",那么聚合就是 "看趋势" 。聚合分析是 ES 的核心进阶功能,本质是"对检索结果或全量数据进行分组、统计、计算"。
为什么需要聚合?
- 日志分析:统计各状态码的请求量
- 电商报表:按品牌统计商品平均价格
- 用户画像:按年龄段、地域分组统计
ES 的聚合基于倒排索引和 doc_values 实现,具备高并发、低延迟的特性,适合实时分析场景。
6.2 聚合的三大分类
| 类型 | 功能 | 类比 SQL |
|---|---|---|
| Metric(指标) | 计算数值指标(avg、sum、min、max、cardinality) | SELECT AVG(price) |
| Bucket(分桶) | 将文档分组(按日期、城市、状态) | GROUP BY city |
| Pipeline(管道) | 对其他聚合结果进行二次计算 | 窗口函数或子查询 |
常见 Bucket 聚合 :terms(最常用)、range、date_histogram
常见 Metric 聚合 :avg、sum、min、max、cardinality(去重计数)
6.3 面试必问:terms 聚合为什么是近似结果?
terms 聚合默认是近似结果,不是绝对精确。因为:
- 每个 shard 先取 top N
- 协调节点再合并各 shard 的结果
如果某个词在单个 shard 中未进入 top N,但在全局范围内应进入 top N,就会被遗漏。
解决方案 :调大 shard_size 参数(shard_size > size),牺牲性能换精度。
6.4 聚合执行流程
- Query 阶段:bool/filter 先筛选命中文档
- 每个 shard 本地执行聚合:分桶 + 计算指标
- 协调节点 merge:合并各 shard 的 bucket
- 如有 Pipeline 聚合:在协调节点对 bucket 再做一次计算
⚠️ 聚合 = CPU + 内存密集操作。shard 越多、bucket 越多,代价越高。
七、分布式架构与集群高可用
7.1 核心概念
ES 采用分布式架构,由多个 Node 组成 Cluster。每个索引被切分为多个 分片(Shard) 。
| 概念 | 说明 |
|---|---|
| Cluster(集群) | 多个节点组成的整体 |
| Node(节点) | 集群中的一个 ES 实例 |
| Index(索引) | 逻辑数据容器 |
| Shard(分片) | 索引的物理切分单元,每个分片是一个独立的 Lucene 实例 |
| Replica(副本) | 主分片的完整拷贝 |
7.2 节点角色
| 节点类型 | 核心职责 |
|---|---|
| Master Node(主节点) | 管理集群状态、索引创建/删除、分片分配 |
| Data Node(数据节点) | 存储数据分片,执行数据读写 |
| Coordinating Node(协调节点) | 接收用户请求、转发请求、汇总结果 |
生产最佳实践 :超过 5 节点的集群必须角色分离------候选主节点独立部署,不承担数据存储压力。
7.3 分片与副本
分片(Primary Shard) :
- 文档写入的唯一入口
- 索引创建时指定数量,创建后不可修改(这是面试高频考点!)
- 如需修改,只能重建索引
副本(Replica Shard) :
- 主分片的完整拷贝
- 高可用保障:主分片故障时,副本可提升为新主分片
- 读负载均衡:副本可承接读请求
- 数量可动态修改
- 必须与主分片分布在不同的节点上
7.4 高可用机制
- Master 选举:基于 Raft 共识算法
- 脑裂防护 :配置
minimum_master_nodes = (候选主节点数 / 2) + 1 - 故障自愈:节点故障 → 副本升主 → 新副本补齐 → 分片重平衡
- 快照备份:支持增量备份至 HDFS、对象存储
- ILM 索引生命周期管理:热-温-冷-冻分层存储
八、读写流程原理
8.1 写入流程
Client → Coordinating Node → 路由计算 → Primary Shard → 同步 Replica → 返回 ACK
详细步骤:
- 客户端向协调节点发送写请求
- 协调节点对
_id进行哈希路由,计算文档属于哪个分片 - 将请求转发给对应的 Primary Shard
- 数据先写入 memory buffer ,再写入 translog(事务日志)
- 并行同步到所有 Replica Shard
- 所有副本确认后,返回成功给客户端
为什么分片数不能改? 因为路由算法 shard = hash(_id) % number_of_shards 依赖分片数量,改了会导致数据找不到。
8.2 读取流程
Client → Coordinating Node → 路由计算 → Primary/Replica(轮询)→ 返回文档
详细步骤:
- 客户端向协调节点发送读请求
- 协调节点对
_id进行哈希路由,确定分片位置 - 使用 round-robin 轮询算法 ,在 Primary 和所有 Replica 中随机选择一个
- 从选中的分片读取文档并返回
💡 读请求可以从 Primary 或 Replica 读取,实现读负载均衡。
九、生产环境性能优化(加分项)
9.1 分片规划黄金法则
- 单个分片推荐大小:20GB - 50GB
- 过大 → 故障恢复慢
- 过小 → 分片数量过多,元数据管理压力大
- 单个节点分片数 ≤ 磁盘容量(GB) × 20
9.2 索引优化
- 显式定义 Mapping ,关闭动态映射(
dynamic: false或strict) - 禁用不需要的字段 :
"index": false、"doc_values": false - 合理设置刷新间隔 :
index.refresh_interval(写入密集时可调大) - 使用索引模板统一管理 settings 和 mappings
9.3 查询优化
- 使用 Filter 上下文(不参与评分,可缓存)
- 避免深度分页 (
from + size过大),使用search_after或 Scroll - 控制聚合的
size和shard_size,避免高基数字段聚合 - 合理使用
_source:只返回需要的字段
9.4 硬件与 JVM
- filesystem cache 要留足够内存:ES 严重依赖 OS 缓存
- JVM 堆内存不超过 32GB(压缩指针失效)
- 堆内存留一半给 OS 缓存
十、面试高频题汇总
| 问题 | 核心考点 |
|---|---|
| ES 为什么比数据库快? | 倒排索引 + 内存缓存,避免全表扫描 |
| text 和 keyword 区别? | 分词 vs 不分词、聚合排序支持 |
| 主分片数量能改吗? | 不能,创建后不可修改 |
| 副本数量能改吗? | 能,动态修改 |
| ES 是实时搜索吗? | 准实时(NRT),refresh 后才可见 |
| terms 聚合精确吗? | 默认近似,可调大 shard_size |
| 如何处理集群 Yellow/Red? | Yellow:副本不足;Red:主分片丢失 |
| 如何避免脑裂? | 配置 minimum_master_nodes |
| RestHighLevelClient 还能用吗? | 7.15 弃用,8.0 移除,推荐 Java API Client |
以上便是 Elasticsearch 从核心原理到生产实践的完整知识体系。掌握这些内容,足以应对绝大多数 ES 相关的面试与日常工作场景。