Elasticsearch内部结构简介

Elasticsearch 是(且不仅仅是)企业搜索的领先解决方案之一。因此,有必要了解它的内部运作方式,以便更好地利用其功能。让我们通过一个简短的旅程来了解 Elasticsearch 的内部工作原理。

Lucene
Apache Lucene 库是一个用于全文索引的开源库。许多应用程序都使用它来构建高级搜索功能,Elasticsearch 就是其中之一。许多其他企业搜索应用程序(如 Apache Solr)也使用 Lucene。

为什么要选择使用 Apache Lucene 或在其基础上构建的搜索应用程序来实现搜索功能?为什么不在应用程序已经使用的关系数据库上使用简单查询呢?答案的关键在于 Apache Lucene 使用的基本数据结构:倒排索引。

简而言之,当我们在 Lucene 中存储(索引)文本(文档)时,它会被分割成标记。倒排索引中的每个不同标记都指向包含它的文档。这就为实现更快的全文搜索算法提供了可能性,它涵盖了更广泛、更复杂的搜索场景。

传统的关系数据库使用基于数据结构(如 B 树)的标准索引来提高性能,但这种索引提供的优化选项较少。
Apache Lucene 如何在内部存储反转索引?它存储在磁盘上称为 Lucene 段的单独文件中。

ELASTICSEARCH:基于 Lucene 的网络服务器...
是的,Elasticsearch 可以被视为建立在 Lucene 库之上的web服务器,甚至是面向文档的数据库。它提供了许多Lucene库本身缺少的重要功能,例如

  • 托管:Elasticsearch 提供了一个强大的机制来构建一个 Elasticsearch 实例集群,以实现可扩展性和高可用性
  • 基于 JSON 的 REST API
  • 缓存
  • 更多

Elasticsearch 中的索引分布在一个或多个主分片和零个或多个副本分片中。实际上,Elasticsearch 分片位于 Elasticsearch 集群的一个节点上,与 Lucene 索引相对应。

Elasticsearch 中的索引可能没有明确定义的字段映射(表结构)。在这种情况下,Elasticsearch 会尝试自动扣除一个。一个字段也可能同时关联多个类型(如文本和关键字)。

每个返回的搜索文档都要进行评分,以确定该文档与搜索查询的相关程度。Elasticsearch 的早期版本(5.0 之前)使用 tf-idf 算法来确定相关性得分,而后来的版本则使用 Okapi BM25 算法。

Elasticsearch 在设计时考虑到了聚类。它会尽量平衡集群中各节点的分片数量,以便均匀分布负载。即使是复制分片也可以参与搜索查询,而不仅仅是提供高可用性。文档的分片是根据文档路由密钥(默认情况下是文档 ID)的简单散列函数确定的:

shard = hash(routing_key) % number_of_primary_shards

向群集添加新节点有两种选择:使用组播地址或单播:使用群集中一个以上现有节点的列表。

处理潜在冲突的机制是通过乐观锁定来实现的。这是通过明确指定当前索引中的文档版本来实现的,如果不是这样,操作就会失败。相比之下,传统的关系数据库采用的是悲观锁定,即锁定模式的某些部分,以防止意外修改。Elasticsearch 的情况并非如此:我们无法在写入请求期间锁定索引或索引的某些部分。

有关分片数量和索引大小的一些一般性建议如下:

  • 分片数量太少会带来可扩展性瓶颈
  • 分片过多会带来性能和管理开销
  • 应根据前期规划确定主分片的数量
  • 应避免将大量数据放在单个索引中:如果需要,应将索引拆分成月/周/日索引,以便将每个索引的数据量保持在 5 至 10 GB 之间。
  • 应尽量使用别名来引用索引

Elasticsearch 集群如何处理请求?
让我们先看看索引请求是如何处理的:

  1. 将索引请求发送至群集中的协调节点
  2. 协调节点将请求路由到分片
  3. 默认情况下,分块不会立即将文档写入磁盘(可通过参数强制写入),而是写入两个内存区域:内存缓冲区和事务日志
  4. 然后将内存区域刷新到磁盘上

现在让我们看看如何处理搜索请求:

  • 搜索请求的处理分为两个阶段:获取和查询
  • 在获取阶段,搜索请求由协调节点转发给所有分片,以确定哪些分片包含与查询匹配的数据
  • 在查询阶段,对这些分片进行查询,检索数据,然后由协调节点汇总并返回给客户端

模块无处不在...
Elasticsearch 节点内部由不同的模块组成。早期版本的 Elasticsearch 使用修改版的 Google Guice 库进行依赖注入。实际上,Elasticsearch 的最新版本已经摒弃了这一做法。模块被绑定到一个 Guice 绑定器上,这样就能有效地在需要的地方注入和使用模块。

一些核心模块包括

  • 发现和集群形成:用于节点发现
  • HTTP:用于 HTTP REST API
  • 插件:用于管理 Elasticsearch 插件
  • 线程池:Elasticsearch 内部使用的线程池
  • 传输:Elasticsearch 节点的通信层

不断发展的代码库 ...
Elasticsearch 的开源版本可以从官方 Github repo 上克隆。每个版本都有相应的标签(如 v8.4.3),次版本(如 8.5)也有分支。代码结构合理,易于理解。下面是 repo 的一些根文件夹:

  • 客户端:低级和高级 Java REST 客户端的实现
  • distribution:用于构建各种发行版(即 RPM)的 gradle 构建脚本
  • docs:以 asciidoc 格式组织的 Elasticsearch 官方文档
  • server:Elasticsearch 核心应用程序,包含内置核心模块
  • x-pack:XPack 扩展的实现
  • 插件:Elasticsearch 发行版中的附加插件
  • 模块:实现 Elasticsearch 功能的附加模块

要了解 Elasticsearch 如何启动,您可以从 org.elasticsearch.node.Nodestart() 方法开始