ES 对 Luence 数据的封装说明

一个**Elasticsearch****Index**多个**Shard**组成,每一个**Shard**都对应着**Luence**的一个**Index**,而**Luence****Index**又由多个**Segment**组成,每个Segment都是倒排索引的集合。
ES 对 Luence 数据的封装说明.png

数据存储位置解剖

对内存空间的使用并非直来直往的结构,而是划分了挺多的层次,这对许多应用都是同理的。
点击查看【processon】
Elasticsearch会涉及到这三个空间的读写。

基本名词

概念

  • document,既然看到这篇文章了,那必然是对Elasticsearch有一定的了解了。它表示了一个新增的文档记录。
  • translog,类似MySQLbinlog,用于记录每条操作的日志。
  • segment file,在本文中segment file表示存储在磁盘空间里的segmentsegment是存储在内存空间的。
  • commit point,记录当前哪些segment已经提交了

操作

  • refreshElasticsearch的操作,将数据从用户空间刷新到内核空间,即PageCache
  • flushElasticsearch的操作,将所有内存中的数据刷到磁盘中去
  • fsync,系统调用,将内存数据写入到磁盘中

Elasticsearch写操作

当客户端发起 写文档 请求后:

  1. 文档处理后被存储到 Elastichsearch 进程空间的Buffer中(在Buffer 中的数据无法直接被搜索到)
  2. 记录 写操作 到 translog 中,这个translog是一直追加的。触发translog刷盘有几个操作:
    1. 每隔 5s fsync到磁盘里去
    2. 当内存中的translog增长到一定体积时自动触发刷盘
    3. translog每30分钟会执行一次flush
  3. 当触发了refresh操作时(?refresh=true的API、index.refresh_interval等操作会触发),会触发以下几个动作:
    1. Buffer的数据写入新的segment,这个segment的体积必然很小(就是tiny segment)存在于Page Cache
    2. 清空buffer
    3. 这个时候segment虽然还没刷盘,但是已经可以被搜索到了
  4. 当触发了flush操作时(?flush的API、每隔30分钟自动flush等操作会触发),会触发以下几个动作:
    1. 清空buffer,将buffer中的数据放到一个新的tiny segment存入Page Cache
    2. Page Cache中的数据全都刷盘
    3. 记录到commit log
    4. 删除硬盘上的translog,并重新开始新的一轮translog

总结几个关键要素,documentPUT进来后,首先会被存放在 Buffertranslog 中;当执行 refresh 操作时, document 就会被追加到一个新建的 tiny segment (同期的其他document也会被加入进来),然后存储在Page Cache中;当发生 flush 操作时,所有 Page Cache 中的 segment 都会刷新到磁盘里去,并记录下commit point,最后清空硬盘上原来的 translog 并开始一轮新的translog

translog 的作用,每次操作都得记录到translog中,translog每隔5秒刷盘一次(刷盘时并不清空translog),当translog体积达到一定程度(或者已经30分钟没有flush了),则执行flush操作,将目前所有的数据都持久化,然后删除旧的translog,使用新的translog。

当发生了异常要重启elasticsearch时,elasticsearch会去取磁盘上的translog和磁盘上的commitpoint进行比较,笔者猜测里面会有个ID,用来判断谁大谁小的问题。如果translog > commitpoint,那么回放translog,把数据持久化到segment中;如果translog == commitpoint,那么相安无事,正常启动(但其实有可能丢了5秒的数据了);如果translog < commitpoint,应该会报异常,这个不是很确定。

Elasticsearch删除与更新操作

Elasticsearch中所有的段只要被持久化了就不支持在原文档基础上更新删除
当发生了 更新 或者 删除 时,则在.del文件里记录下这个document(逻辑删除),虽然 ES 执行查询时依然会查询到这些(被删除)的记录,但是返回结果时会在 **.del**文件中过滤

针对更新的文档来说,新的文档会被加入到新的段中,所以查询时,新的、老的都会被查询到,但是返回结果时会在.del文件中过滤,最终只会返回新的document

每个commit point里都会包含上.del信息,然而 物理删除数据 并不发生在commit point的更新上。而是发生在段合并中。

Elasticsearch段合并

首先为什么会有段合并?原因有以下几个:

  1. 每次refresh都会产生新的segment,即tiny segment
  2. refresh频繁的情况下,tiny segment数量会在短时间内暴增
  3. 每个tiny segment都会占用一个文件句柄部分内存以及CPU时间片
  4. 每次查询都得轮流检查每个segment,所以段越多,查询的越慢

那么Elasticsearch则通过在后台进行 段合并技术 来解决以上几个问题,段合并很容易理解——合并进程选择几个大小相似的segment(可以部分在内存中,部分在磁盘上),然后在后台合并为更大的segment(不影响新增和搜索)。示意图如下所示:

合并过程中不会把 被删除的文档、被更新的文档的旧版本 合并到新的大段中。

image.png
《两个提交了的段和一个未提交的段正在被合并到一个更大的段》
一旦合并结束,就会把新的段flush到磁盘中,然后更新commit point,即 移除旧的被合并的段 并 写入新的段 到commit point中。随后打开新的段用于搜索,老的段被删除。示意图如下所示:
image.png
《一旦合并结束,老的段被删除》
注意,合并操作也是很费资源的,需要考虑一些优化操作。本篇主要分析原理,具体的优化过程往后学到了再列出来。

参考资料