|NO.Z.00039|——————————|BigDataEnd|——|Hadoop&ElasticSearch.V39|——|ELK.v39|原理剖析|索引|
一、Elasticsearch之原理剖析
### --- 倒排索引
~~~ Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。
~~~ 一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
### --- 例如,假设我们有两个文档,每个文档是如下内容:
1. The quick brown fox jumped over the lazy dog
2. Quick brown foxes leap over lazy dogs in summer
二、创建倒排索引
### --- 创建倒排索引示例
~~~ 要创建倒排索引,首先要将每个文档内容拆分成单独的词,
~~~ 创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:
Term Doc_1 Doc_2
-------------------------
Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |
------------------------
### --- 现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:
~~~ 两个文档都匹配,但是第一个文档比第二个匹配度更高。
~~~ 如果我们使用仅计算匹配词条数量的简单 相似性算法,
~~~ 那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。
Term Doc_1 Doc_2
-------------------------
brown | X | X
quick | X |
------------------------
Total | 2 | 1
一、读写流程解析
### --- 创建文档
~~~ 向ES中添加一个文档对象,由于ES是分布式集群并且底层设计为一个索引有众多shard(分片),
~~~ 所以添加文档时需要确定该文档属于哪个分片,确定的规则为:
~~~ shard = hash(routing) % number_of_primary_shards
~~~ routing是一个可变值,默认是文档的_id,也可以设置成一个自定义的值。
~~~ routing通过hash函数生成一个数字,
~~~ 然后这个数字再除以number_of_primary_shards(主分片的数量)后得到余数 。
~~~ 这个分布在0到number_of_primary_shards - 1之间的余数,
~~~ 就是我们所寻求的文档所在分片的位置。
二、写文档流程
### --- 写文档流程:以官网的例子进行分析,从图中能看出一个集群由三个节点组成,有两个分片,两个副本。
~~~ 写操作必须在主分片上面完成之后,
~~~ 才能被复制到其他节点作为分片副本,新建、索引和删除请求都是写操作。
~~~ 客户端向master发送写入请求,该节点作为协调节点;
~~~ 根据文档的_id确定分片,图中请求文档属于分片0,协调节点请求转到节点的主分片;
~~~ 在节点3上执行请求,成功之后,节点3根据副本数将请求并行转到副本分片对应节点,
~~~ 一旦副本分片执行完成,都向节点3报告成功,节点3将向协调节点报告成功,
~~~ 协调节点再向客户端报告成功。
~~~ 客户端收到成功响应时,则变更操作是安全的。

三、读文档流程
### --- 读文档流程
~~~ 一个搜索请求必须询问请求的索引中所有分片的某个副本来进行匹配。
~~~ 假设一个索引有5个主分片,每个主分片有一个副本分片,共10分片,
~~~ 一次搜索请求会由5个分片来共同完成,他们可能是主分片,也可能是副分片。也就是说,
~~~ 一次搜索请求只会命中所有分片副本中的一个。
~~~ 一次检索流程主要分为两个阶段:
### --- Query阶段
~~~ 客户端发送search请求到NODE3。
~~~ NODE3将查询请求转发到索引的每个主分片或副分片中。
~~~ 每个分片在本地执行查询,并使用本地的Term/Docuemnt Frequency信息进行打分,
~~~ 添加结果到大小为from+size的本地优先队列中。
~~~ 每个分片返回各自优先队列中所有文档的ID和排序值给协调节点,
~~~ 协调节点合并这些值到自己的优先队列中,产生一个全局排序后的列表。

### --- Fetch阶段
~~~ 协调节点向相关NODE发送GET请求。
~~~ 分片所在节点向协调节点返回数据。
~~~ 协调节点等待所有文档被取得,然后返回给客户端。
~~~ 分片所在节点在返回文档数据时,处理有可能出现的 _source字段和高亮参数

一、索引文档写入和近实时搜索原理
### --- 基本概念
~~~ # Segments in Lucene
~~~ 众所周知,Elasticsearch 存储的基本单元是 shard , ES 中一个 Index 可能分为多个 shard,
~~~ 事实上每个 shard 都是一个 Lucence 的 Index,并且每个 Lucence Index 由多个 Segment 组成,
~~~ 每个 Segment 事实上是一些倒排索引的集合, 每次创建一个新的 Document ,
~~~ 都会归属于一个新的 Segment, 而不会去修改原来的 Segment 。
~~~ 且每次的文档删除操作,会仅仅标记 Segment 中该文档为删除状态,
~~~ 而不会真正的立马物理删除, 所以说 ES 的 index 可以理解为一个抽象的概念。 就像下图所示:

### --- Translog-Hbase WAL(Write Ahead Log:预写入日志)
~~~ 新文档被索引意味着文档会被首先写入内存 buffer 和 translog 文件。
~~~ 每个 shard 都对应一个 translog 文件

### --- Refresh in Elasticsearch
~~~ 在 Elasticsearch 中, _refresh 操作默认每秒执行一次,
~~~ 意味着将内存 buffer 的数据写入到一个新的 Segment中,
~~~ 这个时候索引变成了可被检索的。写入新Segment后 会清空内存buffer。

### --- Flush in Elasticsearch
~~~ Flush 操作意味着将内存 buffer 的数据全都写入新的 Segments 中,
~~~ 并将内存中所有的 Segments 全部刷盘, 并且清空 translog 日志的过程。

二、近实时搜索
### --- 近实时搜索
~~~ es 写操作流程,当一个写请求发送到 es 后,es 将数据写入 memory buffer 中,
~~~ 并添加事务日志( translog )。
~~~ 如果每次一条数据写入内存后立即写到硬盘文件上,由于写入的数据肯定是离散的,
~~~ 因此写入硬盘的操作也就是随机写入了。硬盘随机写入的效率相当低,会严重降低es的性能。
~~~ 因此 es 在设计时在 memory buffer 和硬盘间加入了
~~~ Linux 的高速缓存( File system cache )来提高 es 的写效率。
~~~ 当写请求发送到 es 后,es 将数据暂时写入 memory buffer 中,此时写入的数据还不能被查询到。
~~~ 默认设置下,es每1秒钟将 memory buffer 中的数据 refresh 到 Linux 的 File system cache ,
~~~ 并清空 memory buffer ,此时写入的数据就可以被查询到了。

### --- refresh API
~~~ 在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。
~~~ 默认情况下每个分片会每秒自动刷新一次。
~~~ 这就是为什么我们说 Elasticsearch 是 近 实时搜索:
~~~ 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。
~~~ 这些行为可能会对新用户造成困惑: 他们索引了一个文档然后尝试搜索它,但却没有搜到。
~~~ 这个问题的解决办法是用 refresh API 执行一次手动刷新:
~~~ # 1. POST /_refresh
~~~ # 2. POST /my_blogs/_refresh
~~~ # 3. PUT /my_blogs/_doc/1?refresh
{"test": "test"}
PUT /test/_doc/2?refresh=true
{"test": "test"}
~~~ 刷新(Refresh)所有的索引。
~~~ 只刷新(Refresh) blogs 索引
~~~ 只刷新 文档
~~~ 并不是所有的情况都需要每秒刷新。
~~~ 可能你正在使用 Elasticsearch 索引大量的日志文件, 你可能想优化索引速度而不是近实时搜索,
~~~ 可以通过设置 refresh_interval , 降低每个索引的刷新频率
PUT /my_logs
{
"settings": { "refresh_interval": "30s" }
}
~~~ refresh_interval 可以在既存索引上进行动态更新。 在生产环境中,
~~~ 当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来:
PUT /my_logs/_settings
{ "refresh_interval": -1 }
PUT /my_logs/_settings
{ "refresh_interval": "1s" }
~~~输出参数
{
"acknowledged" : true
}
三、持久化变更(flush)
### --- 持久化变更
~~~ 即使通过每秒刷新(refresh)实现了近实时搜索,
~~~ 仍然需要经常进行完整提交来确保能从失败中恢复。
~~~ 但在两次提交之间发生变化的文档怎么办?我们也不希望丢失掉这些数据。
~~~ Elasticsearch 增加了一个 translog ,或者叫事务日志,
~~~ 在每一次对 Elasticsearch 进行操作时均进行了日志记录。
~~~ 通过 translog ,整个流程看起来是下面这样:
### --- 一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog ,如下图描述的一样:
~~~ 新的文档被添加到内存缓冲区并且被追加到了事务日志

~~~ 刷新(refresh)使分片处于 下图描述的状态,分片每秒被刷新(refresh)一次:
~~~ 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作。
~~~ 这个段被打开,使其可被搜索。
~~~ 内存缓冲区被清空。
~~~ 刷新(refresh)完成后, 缓存被清空但是事务日志不会

~~~ 这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志(见 图 23 )。
~~~ 事务日志不断积累文档

### --- 每隔一段时间--例如 translog 变得越来越大--索引被刷新(flush);
~~~ 一个新的 translog 被创建,并且一个全量提交被执行(:
~~~ 所有在内存缓冲区的文档都被写入一个新的段(segment)。
~~~ 缓冲区被清空。
~~~ 一个提交点被写入硬盘。
~~~ 文件系统缓存通过 fsync 被刷新(flush)。
~~~ 老的 translog 被删除。
~~~ translog 提供所有还没有被刷到磁盘的操作的一个持久化记录。
~~~ 当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,
~~~ 并且会重放 translog 中所有在最后一次提交后发生的变更操作。
~~~ translog 也被用来提供实时 CRUD 。当你试着通过 ID 查询、更新、删除一个文档,
~~~ 它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。
~~~ 这意味着它总是能够实时地获取到文档的最新版本。
~~~ 在刷新(flush)之后,段被全量提交,并且事务日志被清空

### --- flush API
~~~ 这个执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush 。
~~~ 分片每 30 分钟被自动刷新(flush),或者在 translog 太大(512M)的时候也会刷新。
~~~ flush API 可以 被用来执行一个手工的刷新(flush):
POST /blogs/_flush
POST /_flush?wait_for_ongoin
~~~ 刷新(flush) blogs 索引。
~~~ 刷新(flush)所有的索引并且等待所有刷新在返回前完成。
~~~ 我们很少需要自己手动执行一个的 flush 操作;通常情况下,自动刷新就足够了。
~~~ 这就是说,在重启节点或关闭索引之前执行 flush有益于你的索引。
~~~ 当 Elasticsearch 尝试恢复或重新打开一个索引,它需要重放 translog 中所有的操作,
~~~ 所以如果日志越短,恢复越快。
~~~ Translog 有多安全? translog 的目的是保证操作不会丢失。
~~~ 这引出了这个问题: Translog 有多安全? 在文件被 fsync 到磁盘前,
~~~ 被写入的文件在重启之后就会丢失。这个过程在主分片和复制分片都会发生。
~~~ 最终, 基本上,这意味着在整个请求被 fsync 到主分片和复制分片的 translog 之前,
~~~ 你的客户端不会得到一个 200OK 响应。 在每次写请求后都执行一个 fsync 会带来一些性能损失,
~~~ 尽管实践表明这种损失相对较小(特别是bulk 导入,它在一次请求中平摊了大量文档的开销)。
~~~ 但是对于一些大容量的偶尔丢失几秒数据问题也并不严重的集群,
~~~ 使用异步的 fsync 还是比较有益的。比如,写入的数据被缓存到内存中,再每 5 秒执行一次 fsync 。
~~~ 这个行为可以通过设置 durability 参数为 async 来启用:
PUT /my_index/_settings {
"index.translog.durability": "async",
"index.translog.sync_interval": "5s"
}
~~~ 这个选项可以针对索引单独设置,并且可以动态进行修改。
~~~ 如果你决定使用异步 translog 的话,你需要 保证 在发生crash 时,
~~~ 丢失掉 sync_interval 时间段的数据也无所谓。请在决定前知晓这个特性。
~~~ 如果你不确定这个行为的后果,
~~~ 最好是使用默认的参数( "index.translog.durability": "request" )来避免数据丢失。
一、索引文档存储段合并
### --- 段合并机制
### --- 两个提交了的段和一个未提交的段正在被合并到一个更大的段
~~~ 由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。
~~~ 而段数目太多会带来较大的麻烦。
~~~ 每一个段都会消耗文件句柄、内存和 CPU 运行周期。
~~~ 更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
~~~ Elasticsearch 通过在后台进行段合并来解决这个问题。
~~~ 小的段被合并到大的段,然后这些大的段再被合并到更大的段。
~~~ 段合并的时候会将那些旧的已删除文档 从文件系统中清除。
~~~ 被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
~~~ 启动段合并在进行索引和搜索时会自动进行。
~~~ # 这个流程像在下图中提到的一样工作:
~~~ 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。
~~~ 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。
~~~ 这并不会中断索引和搜索。

### --- 合并完成时的活动:
~~~ 新的段被刷新(flush)到了磁盘。 写入一个包含新段且排除旧的和较小的段的新提交点。
~~~ 新的段被打开用来搜索。
~~~ 老的段被删除。
### --- 一旦合并结束,老的段被删除
~~~ 合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。
~~~ Elasticsearch 在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行。
~~~ 默认情况下,归并线程的限速配置indices.store.throttle.max_bytes_per_sec 是 20MB。
~~~ 对于写入量较大,磁盘转速较高,甚至使用 SSD 盘的服务器来说,这个限速是明显过低的。
~~~ 对于 ELK Stack 应用,建议可以适当调大到 100MB或者更高。

PUT /_cluster/settings
{
"persistent" : {
"indices.store.throttle.max_bytes_per_sec" : "100mb"
}
}
~~~ 用于控制归并线程的数目,推荐设置为cpu核心数的一半。
~~~ 如果觉得自己磁盘性能跟不上,可以降低配置,免得IO情况瓶颈。
index.merge.scheduler.max_thread_count
二、归并策略 policy
### --- 归并线程是按照一定的运行策略来挑选 segment 进行归并的。主要有以下几条:
index.merge.policy.floor_segment 默认 2MB,小于这个大小的 segment,优先被归并。
index.merge.policy.max_merge_at_once 默认一次最多归并 10 个 segment
index.merge.policy.max_merge_at_once_explicit 默认 optimize 时一次最多归并 30 个 segment。
index.merge.policy.max_merged_segment 默认 5 GB,大于这个大小的 segment,不用参与归并。optimize 除外。
三、optimize API
### --- optimize API
~~~ optimize API 大可看做是 强制合并 API。
~~~ 它会将一个分片强制合并到 max_num_segments 参数指定大小的段数目。
~~~ 这样做的意图是减少段的数量(通常减少到一个),来提升搜索性能。
~~~ 在特定情况下,使用 optimize API 颇有益处。
~~~ 例如在日志这种用例下,每天、每周、每月的日志被存储在一个索引中。
~~~ 老的索引实质上是只读的;它们也并不太可能会发生变化。
~~~ 在这种情况下,使用 optimize 优化老的索引,将每一个分片合并为一个单独的段就很有用了;
~~~ 这样既可以节省资源,也可以使搜索更加快速:
~~~ POST /Logstash-2014-10/_optimize?max_num_segments=1
~~~ forceMergeRequest.maxNumSegments(1)
Walter Savage Landor:strove with none,for none was worth my strife.Nature I loved and, next to Nature, Art:I warm'd both hands before the fire of life.It sinks, and I am ready to depart
——W.S.Landor
分类:
bdv025-elk
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix