前面讲的索引文档、执行查询等 API 操作已经比较细了。但不足以真正了解 elasticsearch 是如何工作的,这就站在一个较高的层次简单介绍一下它的体系结构。
一,全景视角
elasticsearch 是一个用 Java 基于 Apache Lucene 开发的上手即用地服务器端应用。
(一)输入数据
数据能以多种方式从不同的数据源导入到 elasticsearch 中:
以利于搜索和分析的格式进行的分析过程有助于有效地检索数据。一旦摄取的数据通过 Elasticsearch 存储,就可以快速地进行搜索。
(二)处理数据
在 elasticsearch 中,基本的信息单元由 JSON 文档表示:
{
"title":"Is Remote Working the New Norm?",
"author":"John Doe",
"synopsis":"Covid changed lives. It changed the way we work..",
"publish_date":"2021-01-01",
"number_of_words":3500
}
为了容纳数据,elasticsearch 根据不同类型的数据创建一组存储桶,比如新闻文章就被存在自定义的被叫做新闻的桶中,这些文章就是文档,而这些桶就是索引。也就是说,索引就是文档的逻辑集合。
- 用 RDBMS 类比的话,索引就是数据库中的数据表,文档就是表中一行行的记录。
出于性能考虑,可以将索引拆分到集群中不同节点上的分片之中,统一由主分片进行管理,由副分片做数据备份。
具体点来说,文档中的数据字段有不同的类型,通过一个叫做映射的过程将 JSON 格式的文档转为 elasticsearch 可处理的类型,正是这个过程定义了 elasticsearch 如何处理我们的数据字段。
在索引文档期间,elasticsearch 就根据映射的定义来分析数据字段,其中对全文本字段的分析就是构建 elasticsearch 高效搜索的关键。
这个文本分析过程包含两个过程:分词(tokenization)和规范化(normalization)。
分词过程通过一系列规则来拆分文本,通常是拆分为单个的字(token)或者词组——具体由分词器(tokenizers)决定,比如:
分词后用户就能使用 API 进行文本搜索操作了,但分词结果本身并不会持久化,为了提供更高效的搜索,还需要将结果规范化。
规范化会围绕分词结果创建额外的数据来提供良好的用户体验,比如:
规范化之后的内容会存储在成为**倒排索引**的高级数据结构中。
(三)输出数据
数据经过输入与处理后,就可以通过搜索或者查询获得。
当客户端发出一个搜索请求时,如果该字段是一个全文本字段,那么它将在服务器端经历一个与索引该字段期间所执行的类似的分析阶段,然后在倒排索引中搜索和匹配各自的 token,并将匹配的结果转发回客户端。
二,组件
与任何应用程序一样,Elasticsearch 有一堆的组件来组成搜索引擎,包括文档、索引、分片等。
(一)文档
文档是由 Elasticsearch 存储数据 、建立索引信息的基本单元。类似于 RDBMS 中的行记录。
(二)移除关系与产生关系
在 RDBMS 中,“关系”表示数据表之间的关联关系,比如学生表会关联到课程表。
但在 ES 的文档中保存的数据,将“关系”扁平化——各算各的,互不影响。学生文档就放在保存学生“类型”的索引中,有点 RDBMS 中数据表的概的意思。
在 ES 5.X 之前,确实是允许在同一个索引中保存不同类型的文档的,比如汽车索引中,可以保存汽车信息文档、汽车用户文档、甚至汽车销售订单文档等不同类型的文档,比如 cars/carinfo/ 、cars/caruser/ 等。
但从构成 ES 的基石 Lucene 的角度来说,直接基于倒排序索引对文档进行查询本就是干脆直接的,文档类型的存在反倒减慢搜索速度,所以在 ES 7.X 中就完全移除了类型这一概念,用 carinfo/_doc、caruser/_doc 的形式完全扁平化文档,这样做有一些好处:
- 索引过程是快速和无锁的。
- 搜索过程是快速和无锁的。
- 因为每个文档相互都是独立的,大规模数据可以在多个节点上进行分布。
和其它文档数据库一样,ES 文档是否匹配搜索请求取决于它是否包含所有的所需信息。除非有很强的理由,否则总是应该以扁平化的方式建模数据以方便将数据保存为文档,尽量避免数据间产生关系。
但关系信息无处不在,博客有评论吧,银行账户有交易记录吧。各自独立的文档之间确实也能产生关系关联,可通过以下方式来实现:
- 在应用层做拼接。
- 这就需要在关系字段上设置指定的字段类型:Join(父子关系文档),Object 和 Nested(两者表示两种文档嵌套关系)。
- 文档冗余存储。
通常我们会选用第一种方式:先将拆分复杂查询,然后者根据结果进行二次查询,最后拼接结果。
(三)索引与分片
再次说明,索引就是文档的逻辑集合,它会被切分到各个节点中被称为分片的控制下:
从技术上来说,索引能保存无尽的文档。但从实际存储的角度来说,我们应该将比较大的索引切分为相对较小的分片,而且这些分片能存在于多个节点中,这种分布式的架构设计就是产生 ES 高效查询的原因之一。
- 索引的具体大小应该提前进行讨论以满足当前或者未来的需求。
- 对文分片做备份有利于保持系统健壮性,向上图那样,对一个 500BG 的分片做两个备份,则该节点上就有 1.5TB 的数据。
每个索引都有各自的属性,比如映射(mappings)、配置(settings)、别名(aliases)等。
(四)rollover 与数据流
对于一个日活量比较大的站点来说,各方面数据量的增长是非常快的。
我们可以添加额外的索引来保存数据以应对搜系统所需的数据量的增长。特别是对依赖时间戳的数据,比如日志数据、动态统计数据等随时间增长持续追加存储的数据——时间序列数据——来说,它们可能被存储到不同的索引当中。
但我们的期望是,时序数据不需要周期性地更新到新的索引,而是当原本索引中的数据量达到一个阈值的时候以一种自动切割的方式直接存到新生成的索引当中,这可以通过 rollover 机制来实现:在目标满足滚动条件时将新数据滚动到新索引。
rollover 可处理两类数据:
🌰订单信息数据流的 rollover 处理:
(五)分片与备份
分片(shard)是存储数据、创建支撑性数据结构(比如倒排索引等)、映射查询和分析数据的组件。
前面说过,Apache Lucene 是构建 ES 的基础,它的所有实例会在创建索引的时候分配给索引。在索引文档的时候,文档会先传给分片的内存缓冲区,在分片获得可写段之后,再由分片在持久化存储系统中存储文档。
集群中每个节点都有一个分片,不同节点间的分片是透明的。
备份(replica)——副分片——都是对分片中数据的冗余。各个分片的备份与该分片不在同一个节点上,避免节点崩溃导致被一锅端。
1,分布式环境中的分片与备份
当创建一个新的名为 virus_mutations 的索引来索引冠状病毒突变数据的时候,假设根据我们的数量设置,它的数据将由三个分片共同提供。
在一个集群中开启第一个节点——形成单节点集群——的时候,只要所有分片都完成初始化,那么节点A将拥有三个分片:
然后就能立即开始索引与搜索操作。但此时还未有备份节点:
为了避免可能的意外造成的数据丢失,我们决定在集群中新开一个节点来做数据备份,这会产生节点的均衡移动:
节点分配好之后,就可以实例化备份了:创建每个分片的一个(多个)克隆体,并将数据从各自的分片复制到这些克隆体中:
2,健康状态
在上面的过程中,我们始终在注意集群的健康状态:
3,数据恢复
假设节点A崩了,那么 ES 会将作为 S1 副本的 R1 提升为 S1:
一旦 DevOps 启动节点A,分片将被重新分配,并对应实例化备份,以便系统尝试达到健康的绿色状态。
4,分片的容量尺寸
分片应该保持多大的容量?
这里没有适合所有人的万能方案。应根据当前的数据要求和未来的需要,提前进行规模测试以得到合适的结果。
官方建议的分片大小是10GB~50GB。业界的普遍做法是将单个分片的大小设置为不超过50GB。
- GitHub 的索引分布在128个分片上,每个分片120GB。
如何调整分片大小,有一个参数可以参考:堆内存。
节点拥有有限的计算资源,如内存和磁盘空间。可以根据可用内存对每个 Elasticsearch 实例进行调整,以高效使用堆内存。比较合适的做法是每 GB 堆内存最多托管20个分片。
- 默认情况下,Elasticsearch 使用 1gb 内存来完成实例化,可以修改 ES 配置文件中的 jvm 项来更改这个设置。
总之,分片是用来保存数据的,因此我们必须进行初始工作以获得正确的大小。大小取决于索引保存了多少数据(包括未来的需求),以及我们可以分配给一个节点多少堆内存,同时在加入数据之前都必须选择适当的分片策略。以在数据需求和最佳分片数量之间取得平衡。
(六)节点与集群
1,节点与集群的关系
当首次启动 ES 时,也就启动了被称为节点的单个 ES 实例,这个节点在一个集群之中,所以这个集群是一个单节点集群:
而生产环境中,一个集群可以通过水平扩展或垂直扩展的方式拥有多个节点。就像我们之前说的那样,用集群中的多个节点保存拆分的分片,同时做相应的备份。
当然也能创建多集群:
2,节点的数据类型与角色
通常来说,一个项目会拥有不同类型的数据,比如跟销售相关的票据信息、用户信息、订单信息等,跟基础设施相关的服务器日志、数据库日志等。
最佳策略是为不同的数据创建多个集群,并为每个集群定制配置。例如,业务关键型数据集群可能运行在配置了较高内存和磁盘空间选项的内部集群上,而应用程序监控数据集群的设置略有不同。
每个节点都需要承担特定的责任。有些可能需要关注与数据相关的活动如索引和缓存,有些可能需要协调客户机的请求和响应,有些可能还负责节点到节点的通信和集群级别的管理。
ES 使用一套角色系统来为保存不同数据的节点分配角色:
三,倒排索引
在前面提到过 ES 将分析好的全文字段存储在一种称为倒排索引的高级数据结构中。这就来了解倒排索引的内部工作原理,同时巩固文本分析过程、存储和检索过程。
在有些工具书中,有一个所以列表用来标注某些关键字出现在书中的哪些位置,比如《新华字典》的查字表,倒排索引就是这样的数据结构:
当 ES 接受搜索请求时,就跟通过倒排索引在查询关键字与目标文档的对应关系,一旦存在对应关系,就会按搜索要求返回指定的文档内容。
具体来看看到排序索引的工作方式。
举个例子,我们有两个拥有全文本类型字段 greeting 的文档:
//Document 1
{
"greeting":"Hello, World"
}
//Document 2
{
"greeting":"Hello, Mate"
}
在 ES 中分析过程是由分析器模块执行的复杂功能。分析器模块进一步由字符过滤器(character filters)、分词器(tokenizer)和分词过滤器(token filter)组成。
当索引文档 1 后,文档 1 内全文本字段中的值会被分词并规范化,此时 “Hello, World” 就被处理为两个字 “Hello” 和 “World”:
分析完成后,ES 就会为这个分析结果创建到排索引并保存结果与文档之间的映射关系。本质来说,倒排索引就是一个哈希表,它能根据关键字找出包含该关键字的文档。
此时倒排索引中的内容如下:
当文档 2 也分析完之后,倒排索引中的内容如下:
四,相关性
现代搜索引擎不再是只返回搜索的结果,而是根据相关性(Relevancy)返回最相关的结果。
相关性的直接体现就是前面我们讲过的 _score
的分数高低。
(一)相关性算法
ES 默认使用 BM25 算法为搜索结果评分。
当匹配到多个结果是就要为结果打分。ES 使用 field norm length 算法。
(二)相似性算法
除了提供 BM52 计算相关性之外,ES 还提供了一个名为 similarity 的模块来计算相关性。
五,路由
现在我们大概知道 ES 的输入、分析和输出这三个过程,还知道集群、节点和备份的概念。
统一前三个过程与后三个概念的关键环节就是,ES 是如何知道哪些文档应存到或者存在于哪个特定的主分片中的?
ES 使用路由算法在索引文档时将文档分发到具体分片。
路由算法是一个简单的公式,ES 在索引或搜索期间推算文档所在分片:
shard_number = hash(id) % number_of_shards
- hash 函数接收文档ID。
- number_of_shards 应该是事先配置好的。一旦索引创建成功后,就不应该再变动(做备份的分片是可以随时改变的)。
分片可能会随着数据的激增而耗。但一个非常有用的解决方式就是先关闭索引再重建索引。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/98042.html