文章目录
- 一、逻辑设计
- 1.文档
- (1)层次结构
- (2)灵活性
- (3)不显示指定字段类型的弊端
- 2.索引
- (1)索引的映射
- (2)索引的管理和维护
- (3)索引的生命周期管理(ILM)
- (4)索引刷新
- 二、物理设计
- 1.节点和分片
- (1)节点(Node)
- (2)分片(Shard)
- 2.当写入、搜索索引的时候发生了什么
- (1)写入
- (2)搜索
- 3.倒排索引的结构
- 词项表(Term Dictionary)
- 倒排列表(Posting List)
一、逻辑设计
1.文档
文档是ElasticSearch索引、搜索的最小单位,它以JSON格式存在。
(1)层次结构
在Elasticsearch中,文档(Document)以JSON格式存在。虽然文档自身没有所谓的“层次结构”,但是通过文档的设计和组织方式,可以体现出一种层次化的数据模型。
可以将相关联的数据直接嵌入到同一个文档中。例如,假设我们有一个博客系统,每个博客文章都有多个评论。可以设计一个文档来表示文章,其中包含一个数组字段,用来存储所有评论的信息。这种方式的优点是查询效率高,因为所有相关数据都在同一个文档中;缺点是更新频繁的数据(如评论)可能会导致整个文档频繁重写。
示例文档:
{"title": "Elasticsearch基础","author": "张三","content": "Elasticsearch是一个强大的全文搜索引擎...","comments": [{"user": "李四","message": "非常有用的文章!"},{"user": "王五","message": "期待更多这样的教程。"}]
}
(2)灵活性
Elasticsearch 的文档(Document)不依赖于预先定义的模式(Schema),这是相对于传统的关系型数据库而言的一个重要特点。在关系型数据库中,表的结构(即模式)需要在插入数据之前明确地定义好,包括每个字段的名称、数据类型等。而在 Elasticsearch 中,文档可以更加灵活地存储和检索,主要体现在以下几个方面:
- 动态映射(Dynamic Mapping)
Elasticsearch 支持动态映射,这意味着当我们第一次索引一个文档时,Elasticsearch 会根据文档的结构自动推断出字段的数据类型,并为这些字段创建映射。例如,如果索引了一个包含 name
字段的文档,Elasticsearch 会自动识别 name
是一个文本字段,并相应地为其创建映射。
示例:
PUT /my-index/_doc/1
{"name": "John Doe","age": 30,"email": "john.doe@example.com"
}
在这个例子中,Elasticsearch 会自动创建一个映射,定义 name
为 text
类型,age
为 integer
类型,email
为 keyword
类型。
- 显式映射(Explicit Mapping)
虽然 Elasticsearch 支持动态映射,但在某些情况下,我们可能希望手动定义映射,以确保字段的数据类型和索引设置符合需求。可以通过在创建索引时定义映射来实现这一点。
示例:
PUT /my-index
{"mappings": {"properties": {"name": { "type": "text" },"age": { "type": "integer" },"email": { "type": "keyword" }}}
}
- 多态性(Polymorphism)
在 Elasticsearch 中,同一个索引中的文档可以有不同的结构。这意味着我们可以在同一个索引中存储不同类型的数据,而不需要预先定义所有可能的字段。这种灵活性使得 Elasticsearch 非常适合处理半结构化和非结构化数据。
示例:
PUT /my-index/_doc/1
{"name": "John Doe","age": 30,"email": "john.doe@example.com"
}PUT /my-index/_doc/2
{"title": "Elasticsearch基础","author": "张三","content": "Elasticsearch是一个强大的全文搜索引擎..."
}
在这个例子中,索引 my-index
包含了两种不同结构的文档,一个表示用户信息,另一个表示文章信息。
- 字段类型检测
Elasticsearch 在索引文档时会自动检测字段的类型,并根据类型进行相应的处理。例如,文本字段会被分析成多个词项,以便进行全文搜索;数值字段则可以直接用于范围查询等。
- 动态更新映射
即使在索引已经存在的情况下,我们也可以动态地更新映射。例如,可以添加新的字段或修改现有字段的设置。这使得 Elasticsearch 的映射非常灵活,可以根据需要随时调整。
示例:
PUT /my-index/_mapping
{"properties": {"address": { "type": "text" }}
}
在这个例子中,我们在已存在的索引 my-index
中添加了一个新的字段 address
。
(3)不显示指定字段类型的弊端
Elasticsearch 的动态映射功能确实提供了极大的灵活性,使得数据可以快速地被索引和搜索。然而,自动猜测字段类型也带来了一些潜在的问题和弊端。以下是一些常见的问题:
- 类型推断错误
Elasticsearch 在首次遇到某个字段时会根据其值来推断字段类型。如果初始值不符合预期,可能会导致类型推断错误。例如:
数值类型误判:如果一个字段的初始值是 "123"
(字符串),Elasticsearch 可能会将其推断为 text
类型,而不是 integer
或 long
类型。
日期类型误判:如果一个字段的初始值是一个不符合标准日期格式的字符串,Elasticsearch 可能会将其推断为 text
类型,而不是 date
类型。
示例:
PUT /my-index/_doc/1
{"age": "30" // 字符串
}PUT /my-index/_doc/2
{"age": 31 // 数值
}
在这种情况下,age
字段将被推断为 text
类型,后续尝试索引数值类型的 age
会导致类型冲突。
- 类型冲突
当同一个字段在不同的文档中有不同的数据类型时,可能会导致类型冲突。Elasticsearch 不允许同一个字段在同一个索引中有多种类型(除了 text
和 keyword
组合)。
示例:
PUT /my-index/_doc/1
{"price": 100.50 // 浮点数
}PUT /my-index/_doc/2
{"price": "100" // 字符串
}
在这种情况下,price
字段的类型冲突会导致索引失败。
- 性能问题
动态映射虽然方便,但每次索引新字段时都需要进行类型推断,这会增加一定的开销。特别是在大规模数据导入时,动态映射可能导致性能下降。
- 索引大小增加
动态映射可能会导致不必要的字段被索引,从而增加索引的大小。例如,如果一个字段的值总是相同的,或者某些字段不需要被搜索,动态映射可能会导致这些字段被索引,占用额外的存储空间。
- 数据一致性问题
动态映射可能导致数据一致性问题。如果不同文档中的相同字段有不同的类型,查询结果可能会不一致。例如,一个字段在某些文档中是 text
类型,在其他文档中是 keyword
类型,这会导致查询结果难以预测。
- 维护困难
随着数据量的增长,动态映射可能会导致映射变得复杂和难以维护。特别是当需要对现有字段进行修改时,可能会遇到各种限制和问题。
- 解决方案:
为了避免这些问题,可以采取以下措施:
- 显式定义映射:在索引创建时显式定义字段类型和映射,确保字段类型符合预期。
- 使用模板:使用索引模板来预定义常见字段的映射,确保一致性。
- 数据验证:在索引数据之前进行数据验证,确保字段类型正确。
- 定期检查映射:定期检查和优化索引映射,确保其符合当前的需求。
2.索引
在 Elasticsearch 中,索引(Index)是一个非常核心的概念,它是存储相关文档的容器。索引可以类比于关系型数据库中的表,但它的功能和用途更加广泛。
- 定义:索引是具有相似特征的文档集合。每个索引都有一个唯一的名称,用于标识和引用。
- 用途:索引用于存储和检索文档。索引可以被分割成多个分片,以提高查询性能和数据的可扩展性。
(1)索引的映射
映射定义了索引中字段的类型和其他元数据。映射可以是动态的(Elasticsearch 自动推断字段类型),也可以是显式的(手动定义字段类型)。
示例:显式定义映射
PUT /my-index
{"mappings": {"properties": {"name": { "type": "text" },"age": { "type": "integer" },"email": { "type": "keyword" },"created_at": { "type": "date" }}}
}
(2)索引的管理和维护
- 索引别名(Aliases)
索引别名可以用于引用一个或多个索引,便于管理和查询。
示例:创建索引别名
POST /_aliases
{"actions": [{"add": {"index": "my-index","alias": "my-alias"}}]
}
- 索引模板(Templates)
索引模板用于预定义索引的设置和映射,适用于多个索引。
示例:创建索引模板
PUT _index_template/my-template
{"index_patterns": ["my-index-*"],"template": {"settings": {"number_of_shards": 3,"number_of_replicas": 2},"mappings": {"properties": {"title": { "type": "text" },"author": { "type": "text" },"content": { "type": "text" },"published_date": { "type": "date" }}}}
}
(3)索引的生命周期管理(ILM)
Elasticsearch 提供了索引生命周期管理(ILM)功能,用于自动化索引的管理和维护,包括创建、热温冷迁移、合并、删除等操作。
示例:创建 ILM 策略
PUT _ilm/policy/my-policy
{"policy": {"phases": {"hot": {"min_age": "0ms","actions": {"rollover": {"max_size": "50GB","max_age": "30d"}}},"delete": {"min_age": "90d","actions": {"delete": {}}}}}
}
(4)索引刷新
在 Elasticsearch 中,refresh_interval
是一个重要的设置,用于控制索引的刷新频率。刷新操作将最近写入的文档从内存中的缓冲区同步到分片的可搜索状态,使得这些文档可以被搜索到。理解 refresh_interval
的工作原理和配置选项对于优化 Elasticsearch 的性能和响应时间非常重要。
刷新操作将内存中的缓冲区中的数据同步到分片的可搜索状态。这意味着新索引的文档在刷新后才会被搜索请求看到。默认情况下,Elasticsearch 每秒自动刷新一次索引,但这个频率可以通过 refresh_interval
设置进行调整。
refresh_interval
的作用
提高搜索可见性:较短的 refresh_interval
可以使新索引的文档更快地被搜索到,提高搜索的实时性。
影响性能:较短的 refresh_interval
会增加系统的 I/O 负载,可能会影响写入性能和整体性能。
- 配置
refresh_interval
refresh_interval
可以在索引创建时设置,也可以在索引已经存在的情况下动态调整。
- 在索引创建时设置
refresh_interval
PUT /my-index
{"settings": {"number_of_shards": 3,"number_of_replicas": 2,"refresh_interval": "1s" // 默认值为 1 秒},"mappings": {"properties": {"title": { "type": "text" },"author": { "type": "text" },"content": { "type": "text" },"published_date": { "type": "date" }}}
}
- 动态调整
refresh_interval
PUT /my-index/_settings
{"refresh_interval": "2s" // 将刷新间隔调整为 2 秒
}
-
特殊值
-
-1
:将refresh_interval
设置为-1
可以禁用自动刷新。在这种情况下,索引只有在显式调用刷新 API 或者达到其他触发条件(如段合并)时才会刷新。
PUT /my-index/_settings
{"refresh_interval": "-1" // 禁用自动刷新
}
- 显式刷新
即使禁用了自动刷新,仍然可以使用刷新 API 手动触发刷新操作。
POST /my-index/_refresh
- 最佳实践
实时性要求高的场景:如果应用程序需要很高的实时性,可以将 refresh_interval
设置为较短的时间(如 1 秒)。
写入密集型场景:如果写入操作非常频繁,可以考虑将 refresh_interval
设置为较长的时间(如 30 秒),以减少 I/O 负载。
批量导入数据:在批量导入数据时,可以暂时禁用自动刷新,以提高写入性能,然后在导入完成后手动刷新。
二、物理设计
1.节点和分片
Elasticsearch 是一个分布式的搜索和分析引擎,广泛用于全文搜索、结构化搜索、分析报告等场景。它能够快速地存储、搜索和分析大量的数据。Elasticsearch 的设计是高度可扩展的,这意味着它可以很容易地在多台机器上水平扩展以处理非常大的数据集。
(1)节点(Node)
在 Elasticsearch 中,一个节点就是一个运行着 Elasticsearch 实例的服务器。多个节点可以组合成一个集群来提供高可用性和负载均衡。每个节点都参与到集群的状态管理和数据索引及查询中。节点根据其功能可以分为几种类型:
- 主节点(Master Node):负责轻量级协调工作,如创建或删除索引、分配分片等。为了提高系统的稳定性,通常会设置多个主节点候选者,其中一个被选为主节点。
- 数据节点(Data Node):存储索引的数据,并执行与数据相关的操作,如CRUD(创建、读取、更新、删除)、聚合等。数据节点可以同时是主节点。
- 客户端节点(Client Node):也称为协调节点,主要负责将请求转发给合适的节点,减少主节点的压力。它们不保存数据副本,主要用于路由请求和聚合结果。
- 摄取节点(Ingest Node):负责预处理文档,在索引之前对文档进行一些处理操作,比如解析、转换等。
(2)分片(Shard)
分片是 Elasticsearch 中最小的存储单元,它是一个完整的 Lucene 索引。通过分片,Elasticsearch 可以将一个大的索引分成多个部分,这些部分可以分布在不同的节点上,从而实现水平扩展。分片有两种类型:
- 主分片(Primary Shard):当向 Elasticsearch 中索引文档时,文档会被分配到一个主分片中。每个索引可以有多个主分片,这取决于索引创建时的配置。
- 副本分片(Replica Shard):主分片的副本,用于提供高可用性。如果主分片失败,副本分片可以提升为新的主分片。此外,副本分片还可以分担搜索和读取请求的负载,提高系统性能。
分片的数量是在创建索引时指定的,并且一旦设定就不能改变。然而,副本分片的数量可以在索引创建后动态调整。合理规划分片和副本的数量对于优化 Elasticsearch 集群的性能至关重要。
在默认情况下,可以连接集群中的任一节点并访问完整的数据集,就好像集群只有单独的一个节点。
2.当写入、搜索索引的时候发生了什么
(1)写入
当我们向 Elasticsearch 写入数据(即索引文档)时,Elasticsearch 会经历一系列步骤来确保数据被正确地存储和检索。以下是写入流程的主要步骤:
-
客户端请求:
- 客户端应用程序发送一个 HTTP 请求到 Elasticsearch 集群中的任意节点,这个节点被称为协调节点(coordinating node)。请求中包含了要索引的文档及其目标索引名称。
-
路由确定:
- 协调节点根据文档的
_id
和目标索引的分片策略计算出文档应该被存储在哪个主分片上。
- 协调节点根据文档的
-
主分片写入:
- 协调节点将请求转发给包含目标主分片的节点。
- 目标节点接收请求后,先将数据写入内存缓冲区(memory buffer),同时写入事务日志(translog)以确保数据持久化。
- 如果写入成功,主分片会将操作同步到所有对应的副本分片,以保持数据的一致性。
-
副本分片复制:
- 主分片上的更改被异步地复制到所有副本分片。这一过程是异步的,意味着主分片不会等待所有副本分片确认后再返回响应给客户端。
- 副本分片的复制机制保证了即使某个节点失效,数据也不会丢失,提高了系统的可靠性和可用性。
-
响应客户端:
- 当主分片确认数据已经成功写入并且至少有一个副本分片也成功接收到数据后,主分片会向协调节点返回确认消息。
- 协调节点随后向客户端返回成功响应,表示文档已成功索引。
-
刷新和合并:
- Elasticsearch 会定期(默认每秒一次)执行刷新操作,将内存缓冲区中的数据写入一个新的段文件(segment file),使其可搜索。
- 随着时间的推移,段文件会逐渐增多,Elasticsearch 会在后台自动执行合并操作,将多个小段文件合并成更大的段文件,以优化磁盘使用和搜索性能。
(2)搜索
从 Elasticsearch 索引中检索文档的过程涉及多个步骤,确保高效和准确地返回查询结果。以下是检索流程的主要步骤:
-
客户端请求:
- 客户端应用程序发送一个 HTTP 请求到 Elasticsearch 集群中的任意节点,这个节点被称为协调节点(coordinating node)。请求中包含了查询条件和目标索引名称。
-
查询解析:
- 协调节点接收到请求后,首先解析查询条件,确定需要查询的索引和分片。Elasticsearch 支持多种查询类型,包括全文搜索、术语查询、范围查询等。
-
路由确定:
- 协调节点根据查询条件和索引的分片策略,确定需要查询的主分片和副本分片。Elasticsearch 会尽量选择最近的副本分片来减少网络延迟。
-
分发查询请求:
- 协调节点将查询请求分发到包含相关分片的所有节点。这些节点被称为执行节点(executing nodes)。
-
分片查询:
- 每个执行节点在其本地分片上执行查询。查询在分片级别执行时,会利用**倒排索引(inverted index)**来快速找到匹配的文档。倒排索引是一种数据结构,它将文档中的词项映射到包含这些词项的文档列表。
-
局部结果收集:
- 每个执行节点将查询结果返回给协调节点。结果通常包括匹配文档的元数据(如文档 ID、得分等),而不是完整的文档内容。
-
全局结果合并:
- 协调节点将从各个执行节点返回的局部结果进行合并,生成最终的全局结果。合并过程中,Elasticsearch 会根据查询条件(如排序、过滤等)对结果进行排序和筛选。
-
分页处理:
- 如果查询请求中指定了分页参数(如
from
和size
),协调节点会根据这些参数截取最终结果的一部分返回给客户端。
- 如果查询请求中指定了分页参数(如
-
返回响应:
- 协调节点将最终的查询结果打包成 HTTP 响应,返回给客户端应用程序。
-
说明:
-
倒排索引:倒排索引是 Elasticsearch 快速检索的基础。它将文档中的词项映射到包含这些词项的文档列表,使得查找特定词项的文档变得非常高效。
-
评分机制:Elasticsearch 使用 TF-IDF(Term Frequency-Inverse Document Frequency)或其他评分算法来评估文档的相关性。查询结果通常按评分高低排序,最相关的文档排在前面。
-
缓存机制:为了提高查询性能,Elasticsearch 使用多种缓存机制,包括查询缓存(query cache)和过滤器缓存(filter cache)。这些缓存可以显著减少重复查询的响应时间。
-
近实时搜索:Elasticsearch 提供近实时搜索功能,这意味着新索引的文档在几秒钟内就可以被搜索到。这得益于定期的刷新操作,将内存中的数据写入新的段文件。
-
3.倒排索引的结构
倒排索引(Inverted Index)是信息检索系统(如搜索引擎和全文搜索引擎)中常用的一种数据结构,用于高效地存储和检索文本数据。在 Elasticsearch 中,倒排索引是其核心数据结构之一,用于支持高效的全文搜索和过滤操作。
倒排索引由两部分组成:
- 词项表(Term Dictionary):包含所有唯一的词项(terms)及其元数据。
- 倒排列表(Posting List):每个词项对应一个倒排列表,记录了包含该词项的所有文档及其相关信息。
词项表(Term Dictionary)
词项表是一个有序的、包含所有唯一词项的列表。每个词项通常包含以下信息:
- 词项本身:实际的词项字符串。
- 词频统计:词项在整个文档集合中出现的总次数。
- 文档频率(Document Frequency, DF):包含该词项的文档数量。
- 倒排列表的指针:指向该词项对应的倒排列表的起始位置。
词项表通常使用 B-Tree 或 Trie 树等数据结构来实现,以便于快速查找和插入操作。
倒排列表(Posting List)
倒排列表是一系列包含该词项的文档的列表。每个条目(posting)通常包含以下信息:
- 文档 ID:文档的唯一标识符。
- 词项频率(Term Frequency, TF):词项在该文档中出现的次数。
- 位置信息(Positions):词项在文档中的位置(可选,用于短语搜索和邻近搜索)。
- 偏移量(Offsets):词项在文档中的字节偏移量(可选,用于高亮显示)。
倒排列表通常按文档 ID 排序,以便于快速合并和交集操作。
ElasticSearch索引检索速度很快,但是每次更新数据后都需要维护倒排索引,需要成本。