学习 Elasticsearch:基础介绍与实践
在当今信息爆炸的时代,数据的产生速度呈几何级数增长。如何从海量数据中快速、准确地找到所需信息,并从中发现价值,成为了一个巨大的挑战。传统的数据库系统在处理结构化数据的存储和查询方面表现出色,但在面对非结构化或半结构化数据的全文搜索、实时分析和海量数据扩展时,往往显得力不从心。
正是在这样的背景下,分布式搜索引擎和分析引擎应运而生。其中,Elasticsearch 无疑是佼佼者。它以其卓越的全文搜索能力、强大的实时分析功能以及出色的水平扩展性,迅速在技术领域占据了一席之地,并在日志分析、监控、电商搜索、企业内部搜索等众多场景中得到广泛应用。
如果你正准备踏入 Elasticsearch 的世界,那么这篇详细的文章将为你提供一个坚实的基础,带你从零开始了解 Elasticsearch 的核心概念、工作原理,并通过实践案例掌握基本操作。
第一部分:Elasticsearch 基础概念与核心架构
在深入实践之前,理解 Elasticsearch 的核心概念至关重要。这些概念构成了 Elasticsearch 的基石。
1. 什么是 Elasticsearch?
Elasticsearch 是一个基于 Apache Lucene™ 的分布式、RESTful 风格的搜索和分析引擎。简单来说,它是一个能够快速存储、搜索和分析大量数据的开源平台。
- 分布式 (Distributed): 意味着它可以将数据分散存储在多台服务器上,这带来了高可用性、容错性和水平扩展能力。即使部分节点失效,系统也能继续运行,并且可以通过增加节点来处理更多的数据和查询负载。
- RESTful: 它通过标准的 RESTful API 进行交互,可以使用任何编程语言通过 HTTP 请求与其通信,极大地提高了开发的便捷性。
- 搜索和分析引擎: 这是它的核心功能。它不仅能进行高速、相关的全文搜索,还能对数据进行复杂的聚合分析,从而发现隐藏在数据中的模式和趋势。
- 基于 Lucene: Lucene 是一个高性能、全功能的文本搜索库。Elasticsearch 在其基础上构建,提供了更高级的功能、分布式能力和易于使用的 API。
2. 为什么选择 Elasticsearch?
- 速度快: 利用倒排索引(Inverted Index)等技术,Elasticsearch 能够实现接近实时的搜索响应。
- 全文搜索: 强大的文本分析能力,支持多种语言,能够进行相关性评分,返回最相关的搜索结果。
- 分布式和可扩展: 轻松通过增加节点来扩展集群的处理能力和存储容量,无需停机。
- 高可用性: 数据可以复制到多个节点上,保证了即使节点故障,数据也不会丢失,服务也不会中断。
- 实时分析: 强大的聚合框架允许用户对数据进行复杂的分析,如统计、分组、趋势分析等,并且几乎是实时的。
- 易于使用: RESTful API 和 JSON 文档格式使得开发和集成变得简单。Kibana 等工具提供了友好的用户界面。
- 动态 Schema (Dynamic Mapping): 在很多情况下,无需预先定义数据结构(Schema),Elasticsearch 可以根据文档内容自动推断字段类型。
3. 核心概念解析
理解以下核心概念,就抓住了 Elasticsearch 的本质:
-
文档 (Document):
- Elasticsearch 的最小数据单元。类似于关系型数据库中的一行记录,或者 NoSQL 数据库中的一个文档。
- 每个文档都是一个 JSON 对象。
- 每个文档都属于一个索引 (Index)。
- 每个文档在索引中都有一个唯一的 ID。
-
索引 (Index):
- 一个文档的集合,类似于关系型数据库中的一个数据库(Database)或一张表(Table)。
- 索引名称必须是小写。
- 索引是逻辑上的概念,它由一个或多个分片 (Shard) 组成。
-
类型 (Type) – 历史概念:
- 注意: 从 Elasticsearch 6.0 开始,一个索引只允许有一个类型;在 Elasticsearch 7.0 及以后版本,类型概念被完全移除。当前版本通常不再使用类型。
- 在旧版本中,类型是索引内部的一个逻辑分类,类似于关系型数据库中同一数据库下的不同表。移除类型是为了避免同一个索引中不同类型具有相同字段名但不同映射导致的问题。
- 在学习和使用当前版本时,应忽略 Type 的概念,将 Index 直接视为文档的集合。
-
分片 (Shard):
- 索引被水平分割成若干个分片。
- 每个分片都是一个独立的 Lucene 索引实例。
- 分片的目的是为了解决单机存储容量和性能瓶颈:
- 水平扩展: 索引可以存储比单节点容量更大的数据,通过将数据分布到多个分片上实现。
- 并行处理: 可以在多个分片上并行执行搜索请求,提高查询性能。
- 创建索引时可以指定分片的数量。分片数量一旦确定,通常不能修改(尽管有 reindex 等 workaround)。
- 分片又分为主分片 (Primary Shard) 和副本分片 (Replica Shard)。
-
副本分片 (Replica Shard):
- 主分片的副本。
- 副本分片的目的是为了提供高可用性和读取性能:
- 高可用: 如果主分片所在的节点故障,副本分片可以升级为新的主分片,保证数据不丢失和服务不中断。
- 读取性能: 搜索请求可以由主分片或其任意副本分片处理,分担了主分片的负载。
- 可以为每个主分片配置零个或多个副本分片。副本分片数量可以在运行时动态调整。
-
节点 (Node):
- 运行着一个 Elasticsearch 实例的服务器。
- 节点通过配置相同的集群名称来加入同一个集群 (Cluster)。
- 节点有不同的角色,常见的包括:
- Master-eligible Node: 负责集群的管理任务,如创建/删除索引、管理节点加入/离开集群等。集群会从 Master-eligible 节点中选举出一个 Master 节点。
- Data Node: 负责存储数据(分片)并执行数据相关的操作,如搜索、聚合。
- Ingest Node: 用于在索引文档之前进行预处理,如转换、丰富数据。
- Coordinating Node: 接收客户端请求,并将请求路由到合适的节点上,然后汇聚结果返回给客户端。所有节点默认都是 Coordinating Node。
-
集群 (Cluster):
- 一个或多个拥有相同集群名称的节点集合。
- 集群提供分布式能力、高可用性和可扩展性。
- 集群中的所有数据分散存储在各个节点的分片上。
- 集群有一个唯一的健康状态:绿色 (Green)、黄色 (Yellow)、红色 (Red)。
- 绿色: 所有主分片和副本分片都可用。
- 黄色: 所有主分片可用,但至少有一个副本分片不可用。搜索结果依然完整,但高可用性受损。
- 红色: 至少有一个主分片不可用。部分数据可能无法搜索。
概念总结图示(简化):
“`
Cluster (集群)
├── Node 1 (节点) – Master & Data
│ ├── Shard 1 (主分片) for Index A
│ └── Shard 2 (副本分片) for Index B (replica of Shard 2 on Node 2)
│
├── Node 2 (节点) – Data
│ ├── Shard 2 (主分片) for Index B
│ └── Shard 1 (副本分片) for Index A (replica of Shard 1 on Node 1)
│
└── Node 3 (节点) – Data
└── Shard 1 (副本分片) for Index B (another replica of Shard 2 on Node 2)
└── Shard 2 (副本分片) for Index A (another replica of Shard 1 on Node 1)
Index A (索引) = Shard 1 (Primary) + Shard 1 (Replica 1) + Shard 1 (Replica 2)
Index B (索引) = Shard 2 (Primary) + Shard 2 (Replica 1) + Shard 2 (Replica 2)
Each Shard (分片) contains multiple Documents (文档)
Each Document (文档) is a JSON object
“`
第二部分:搭建与基本操作实践
理解了核心概念,接下来我们进行一些基础的实践操作。
1. 搭建 Elasticsearch 环境
最快捷的方式是使用 Docker。假设你已经安装了 Docker 和 Docker Compose。
创建一个 docker-compose.yml
文件:
“`yaml
version: ‘3.8’
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.2 # 使用一个稳定版本
container_name: elasticsearch
environment:
– discovery.type=single-node # 开发环境使用单节点模式
– xpack.security.enabled=false # 禁用安全(开发演示用,生产环境强烈建议开启)
– ES_JAVA_OPTS=-Xms512m -Xmx512m # 设置内存大小
ports:
– “9200:9200” # HTTP API 端口
– “9300:9300” # 节点间通信端口
volumes:
– esdata:/usr/share/elasticsearch/data # 数据持久化
networks:
– elastic
kibana:
image: docker.elastic.co/kibana/kibana:8.12.2 # 使用与 Elasticsearch 版本匹配的 Kibana
container_name: kibana
environment:
– ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
– “5601:5601” # Kibana UI 端口
depends_on:
– elasticsearch
networks:
– elastic
volumes:
esdata: # 定义数据卷
networks:
elastic: # 定义网络
driver: bridge
“`
在 docker-compose.yml
文件所在目录执行命令启动:
bash
docker-compose up -d
等待几分钟,直到容器启动完毕。
- Elasticsearch 默认运行在
http://localhost:9200
。 - Kibana 默认运行在
http://localhost:5601
。Kibana 提供了一个非常方便的 Dev Tools 界面,可以直接发送 Elasticsearch API 请求,强烈推荐使用。
2. Kibana Dev Tools 使用简介
打开浏览器访问 http://localhost:5601
,进入 Kibana 页面。在左侧导航栏找到 “Dev Tools” (或者中文界面中的 “开发工具”) 并点击进入。
Dev Tools 界面分为左右两栏:左边是编辑器,用于输入 Elasticsearch API 请求;右边是输出窗口,显示请求结果。
你可以像在命令行中使用 curl
一样在这里发送请求,但语法更简洁。例如,查看集群健康状态:
json
GET /_cat/health?v
点击播放按钮发送请求,右侧将显示集群健康信息。
3. 基本 CRUD 操作 (创建、读取、更新、删除)
CRUD 操作是与 Elasticsearch 交互的基础。我们主要使用 RESTful API 来完成。
-
创建/索引文档 (Index Document)
使用
POST
或PUT
方法向指定索引发送 JSON 数据。
*POST /{index}/_doc
: 让 Elasticsearch 自动生成文档 ID。
*PUT /{index}/_doc/{id}
: 指定文档 ID。如果 ID 已存在,则执行更新操作。示例:索引一个新文档(自动生成 ID)
json
POST /my_first_index/_doc
{
"title": "Elasticsearch 基础入门",
"author": "张三",
"publish_date": "2023-10-26",
"tags": ["搜索", "大数据", "NoSQL"],
"content": "这篇文章详细介绍了 Elasticsearch 的核心概念和基本操作..."
}发送请求后,Elasticsearch 会返回一个包含新文档 ID (
_id
)、索引名称 (_index
)、版本 (_version
) 等信息的响应。第一次向my_first_index
索引添加文档时,该索引会被自动创建(如果它不存在且动态映射开启)。示例:索引一个新文档(指定 ID)
json
PUT /my_first_index/_doc/doc_1
{
"title": "Elasticsearch 高级应用",
"author": "李四",
"publish_date": "2024-01-15",
"tags": ["聚合", "性能调优"],
"content": "探讨 Elasticsearch 在海量数据分析和性能优化方面的技巧..."
}
如果再次使用PUT /my_first_index/_doc/doc_1
发送请求,但 JSON 内容有修改,Elasticsearch 会更新该文档,并增加其版本号 (_version
)。 -
读取文档 (Get Document)
使用
GET
方法根据索引名称和文档 ID 获取文档。示例:获取 ID 为
doc_1
的文档json
GET /my_first_index/_doc/doc_1响应会包含文档的源数据 (
_source
) 以及元信息(_index
,_id
,_version
等)。 -
更新文档 (Update Document)
更新文档可以通过两种方式实现:
1. 全量更新: 使用PUT /{index}/_doc/{id}
覆盖现有文档。这是最简单的方式,但需要发送完整的文档内容。
2. 部分更新: 使用POST /{index}/_update/{id}
结合doc
或script
参数进行部分字段更新。这种方式更高效,只需要发送需要修改的部分。示例:部分更新文档
doc_1
json
POST /my_first_index/_update/doc_1
{
"doc": {
"tags": ["聚合", "性能调优", "分布式"]
}
}
这会向doc_1
文档的tags
数组中添加 “分布式”。 -
删除文档 (Delete Document)
使用
DELETE
方法根据索引名称和文档 ID 删除文档。示例:删除 ID 为
doc_1
的文档json
DELETE /my_first_index/_doc/doc_1响应会确认删除操作。请注意,删除操作是软删除,文档不会立即从磁盘移除,而是在后台进行标记,并在后续的段合并过程中清理。
-
删除索引 (Delete Index)
使用
DELETE
方法根据索引名称删除整个索引及其包含的所有文档。示例:删除
my_first_index
索引json
DELETE /my_first_index
警告: 这个操作会永久删除索引及其所有数据,请谨慎使用。
4. 查看索引和集群信息
-
查看所有索引:
json
GET /_cat/indices?v
这会以表格形式显示所有索引的信息,包括健康状态、文档数量、存储大小等。 -
查看集群节点:
json
GET /_cat/nodes?v
显示集群中所有节点的信息。 -
查看特定索引的映射 (Mapping):
映射定义了索引中文档的字段类型以及 Elasticsearch 如何处理这些字段(例如,是否需要进行全文分析)。如果创建文档时没有显式定义映射,Elasticsearch 会尝试自动推断(动态映射)。
json
GET /my_first_index/_mapping
第三部分:搜索实践 (Query DSL)
搜索是 Elasticsearch 的核心功能。Elasticsearch 提供了强大的查询语言,称为 Query DSL (Domain Specific Language),它是基于 JSON 的。搜索请求通常发送到 /{index}/_search
或 /_search
(搜索所有索引) 端点,并在请求体中包含 Query DSL。
搜索请求体通常包含一个顶级的 query
对象,用于定义搜索条件。
1. 基本搜索
-
搜索所有文档:
json
GET /my_first_index/_search
{
"query": {
"match_all": {}
}
}
match_all
查询匹配索引中的所有文档。默认情况下,搜索结果最多返回前 10 个文档。 -
设置返回数量和分页:
使用
size
和from
参数进行分页。size
指定返回的最大文档数,from
指定从哪个偏移量开始返回(从 0 开始)。json
GET /my_first_index/_search
{
"query": {
"match_all": {}
},
"size": 5,
"from": 10
}
这将返回第 11 到第 15 个文档。 -
按字段进行全文搜索 (match query):
match
查询是标准的全文搜索查询。它会对查询字符串进行分析(如分词、小写、去除停用词等),然后查找匹配分析后词项的文档。json
GET /my_first_index/_search
{
"query": {
"match": {
"content": "介绍 概念"
}
}
}
这将搜索content
字段中包含分析后匹配 “介绍” 或 “概念” 词项的文档。Elasticsearch 会根据相关性(如 TF-IDF 或 BM25 算法)对结果进行评分和排序。 -
按字段进行精确匹配 (term query):
term
查询用于查找包含与指定词项完全一致的文档。它通常用于非文本字段(如数字、日期、布尔值)或未被分析处理的文本字段(如 keyword 类型)。term
查询不会对查询字符串进行分析。json
GET /my_first_index/_search
{
"query": {
"term": {
"author.keyword": "张三"
}
}
}
这里使用了author.keyword
。默认情况下,文本字段会被分析并索引为text
类型,同时也会创建一个未分析的子字段keyword
。term
查询在keyword
字段上进行精确匹配。直接在author
(text 类型) 上使用term
查询可能得不到预期结果,因为它会查找精确匹配原始分词结果的词项。
2. 结构化搜索与组合查询
除了全文搜索,Elasticsearch 也擅长结构化数据的过滤和组合查询。
-
范围查询 (range query):
用于查找字段值在指定范围内的文档。常用于数字和日期字段。可以使用
gt
(大于),gte
(大于等于),lt
(小于),lte
(小于等于)。json
GET /my_first_index/_search
{
"query": {
"range": {
"publish_date": {
"gte": "2024-01-01"
}
}
}
}
查找发布日期在 2024-01-01 及之后的文档。 -
布尔查询 (bool query):
bool
查询是 Elasticsearch 中最常用的组合查询。它允许你结合其他查询,使用布尔逻辑 (AND
,OR
,NOT
)。bool
查询可以包含以下子句:
*must
: 文档必须匹配此查询。类似于AND
。这些查询会影响相关性评分。
*filter
: 文档必须匹配此查询。类似于AND
。与must
不同,filter
查询不计算相关性评分,并且会被缓存,因此通常比must
更快,适用于过滤条件。
*should
: 文档应该匹配此查询。类似于OR
。如果文档匹配任何一个should
子句,则被视为匹配。如果must
或filter
子句存在,则should
子句不影响匹配(只影响评分);如果不存在must
和filter
,则至少一个should
子句必须匹配。
*must_not
: 文档不能匹配此查询。类似于NOT
。不计算相关性评分。示例:组合查询
查找发布日期在 2024 年之后,且内容包含“实践”但作者不是“张三”的文档:
json
GET /my_first_index/_search
{
"query": {
"bool": {
"filter": [
{
"range": {
"publish_date": {
"gte": "2024-01-01"
}
}
}
],
"must": [
{
"match": {
"content": "实践"
}
}
],
"must_not": [
{
"term": {
"author.keyword": "张三"
}
}
]
}
}
}
这里range
查询放在filter
中,因为它是过滤条件,不需要计算评分,并且可以利用缓存。match
查询放在must
中,因为它是主要的搜索条件,需要计算相关性评分。term
查询放在must_not
中进行排除。
3. 字段过滤 (Source Filtering)
默认情况下,搜索结果返回整个文档的 _source
字段。如果只需要部分字段,可以使用 _source
参数进行过滤,以减少网络传输和处理开销。
json
GET /my_first_index/_search
{
"_source": ["title", "author"],
"query": {
"match_all": {}
}
}
这将只返回每个匹配文档的 title
和 author
字段。你也可以使用排除 (false
或字段列表)。
第四部分:聚合实践 (Aggregations)
聚合是 Elasticsearch 的分析功能,允许你对数据进行分组、计算统计信息、构建指标等。它类似于 SQL 中的 GROUP BY
语句,但功能更加强大和灵活。聚合请求通常与搜索请求一起发送。
聚合结果在搜索响应的 aggregations
字段中返回。
1. 基本聚合
聚合是嵌套的结构。通常由桶 (Bucket) 和指标 (Metric) 组成。
* 桶 (Bucket): 将文档分组。例如,按国家分组、按价格范围分组等。
* 指标 (Metric): 对每个桶中的文档计算统计信息。例如,计算平均价格、总销售额、最大值、最小值等。
示例:按作者统计文档数量 (Terms Aggregation)
terms
聚合是一种桶聚合,用于按字段的唯一值进行分组。
json
GET /my_first_index/_search
{
"size": 0, # 不需要搜索结果,只关心聚合结果
"aggregations": { # 或者简写为 "aggs"
"docs_by_author": { # 聚合的命名,自定义
"terms": {
"field": "author.keyword" # 按作者的精确值分组
}
}
}
}
响应中会有一个 aggregations
字段,其中包含 docs_by_author
的结果,列出每个作者 (key
) 以及对应的文档数量 (doc_count
)。注意这里使用了 author.keyword
进行精确分组。size: 0
表示不返回搜索匹配的文档,只返回聚合结果,这样更高效。
示例:统计文档数量,并计算每个作者的文档发布年份范围 (Bucket & Metric)
可以在一个桶聚合中嵌套指标聚合或其他桶聚合。
json
GET /my_first_index/_search
{
"size": 0,
"aggs": {
"docs_by_author": {
"terms": {
"field": "author.keyword"
},
"aggs": { # 在作者桶内嵌套聚合
"publish_year_range": { # 计算发布年份范围
"date_range": { # 日期范围聚合,也是一种桶聚合
"field": "publish_date",
"ranges": [
{ "to": "2024-01-01" }, # 2024年前
{ "from": "2024-01-01" } # 2024年及后
],
"format": "yyyy-MM-dd"
}
},
"avg_score": { # 计算平均评分,假设文档有 score 字段
"avg": {
"field": "score"
}
}
}
}
}
}
这个聚合会先按作者分组,然后在每个作者的分组内,再按发布日期范围分组,并计算每个作者所有文档的平均评分。
2. 常见的聚合类型
- Bucket Aggregations:
terms
: 按字段的唯一值分组。range
: 按数值范围分组。date_range
: 按日期范围分组。histogram
: 按固定间隔分组(数值)。date_histogram
: 按固定时间间隔分组(日期)。filter
: 过滤文档后创建一个桶。filters
: 使用多个过滤器创建多个桶。geodistance
: 按距离地理位置的远近分组。
- Metric Aggregations:
count
: 计算文档数量 (通常由桶自动提供doc_count
)。sum
: 计算字段值的总和。avg
: 计算字段值的平均值。min
: 计算字段值的最小值。max
: 计算字段值的最大值。stats
: 同时计算 count, min, max, avg, sum。extended_stats
: 提供更多统计信息,如方差、标准差。cardinality
: 计算字段的唯一值数量(去重计数)。
聚合功能非常强大,可以进行多层嵌套,实现复杂的分析需求。
第五部分:生态系统与进阶方向
Elasticsearch 不仅仅是一个独立的搜索引擎,它是一个完整的生态系统的一部分,最著名的就是 ELK Stack (现在通常称为 Elastic Stack)。
- Elasticsearch: 存储、搜索和分析数据。
- Logstash: 一个动态数据收集管道,可以从各种来源采集数据,进行转换,然后发送到 Elasticsearch。常用于日志和事件处理。
- Kibana: 数据可视化和管理工具。提供用户界面来搜索、查看和与 Elasticsearch 索引中的数据交互,并提供了丰富的图表和仪表盘功能,也包括 Dev Tools。
- Beats: 轻量级数据采集器,用于向 Elasticsearch 或 Logstash 发送各种类型的数据,如日志文件 (Filebeat)、指标 (Metricbeat)、网络流量 (Packetbeat) 等。
ELK/ECK (Elastic Cloud on Kubernetes) Stack 是一个功能强大的平台,用于实时数据采集、处理、存储、搜索、分析和可视化。
进阶学习方向:
掌握了基础概念和 CRUD、搜索、聚合的基本操作后,可以继续深入学习:
- 映射 (Mapping): 深入理解字段类型(
text
,keyword
,integer
,date
,boolean
,geo_point
等),自定义映射,以及动态映射的工作原理。映射是影响搜索和聚合效果的关键。 - 分析器 (Analyzers): 理解文本分析过程(字符过滤器、分词器、词项过滤器),如何选择和定制分析器以优化不同语言或场景的全文搜索效果。
- 相关性评分 (Relevance Scoring): 了解 TF-IDF 和 BM25 等评分算法,以及如何通过查询提升、函数评分等方式影响搜索结果的排序。
- 复杂查询: 学习更多 Query DSL 类型,如短语匹配 (
match_phrase
)、模糊匹配 (fuzzy
)、通配符/正则表达式 (wildcard
,regexp
)、跨字段搜索 (multi_match
) 等。 - 聚合高级技巧: 学习 Pipeline Aggregations (如
bucket_sort
,moving_average
),父子关系和 Join 查询的处理等。 - 集群管理与运维: 学习节点角色、集群健康监控、分片分配、扩容缩容、快照与恢复、安全设置 (X-Pack Security)、性能调优等。
- 数据建模: 如何设计索引结构以满足特定的搜索和分析需求。
- Scroll & Slice Scroll: 处理大量搜索结果的分页。
- Reindex & Update by Query & Delete by Query: 批量操作数据。
- Ingest Pipelines: 在文档索引前进行复杂的预处理。
结语
Elasticsearch 是一个功能强大、应用广泛的平台,掌握它将为你打开处理海量数据的新世界。从核心概念(文档、索引、分片、节点、集群)到基本操作(CRUD、搜索、聚合),再到其丰富的生态系统和进阶特性,Elasticsearch 提供了解决复杂数据问题的有效手段。
学习 Elasticsearch 最好的方式是动手实践。搭建一个本地环境,导入一些数据,然后不断尝试不同的搜索和聚合查询。结合官方文档,逐步深入理解 Query DSL 的各种选项和聚合的丰富功能。
虽然本文提供了详细的介绍和实践指导,但 Elasticsearch 的能力远不止于此。持续学习和探索其高级特性,将帮助你更好地利用这个强大的工具,应对各种数据挑战。
祝你在 Elasticsearch 的学习旅程中取得成功!