Elasticsearch近实时搜索与Translog
Elasticsearch基于Lucene,Lucene搜索是按segment进行的,每一个segment本身就是一个倒排索引,一个Lucene倒排索引包含segment集合和一个提交点(是一个列出了所有已知segment的文件)。新的文档首先被添加到内存的lucene索引缓存中,然后写入到一个基于磁盘的segment,在一次提交后,一个新的segment被添加到提交点并且清空缓存。
一个Lucene索引在ES中就是一个分片,一个ES索引是分片的集合,即一个ES索引是Lucene索引的集合。当ES在ES索引中搜索的时候,它发送查询到每一个属于ES索引的分片(Lucene 索引)上,然后执行分布式检索,合并每个分片的结果到一个全局结果集
(以下提到的索引都是ES索引,Lucene索引表示为segment)
逐段搜索流程:
- 新的文档被搜集到内存索引缓存中
- 缓存不定时进行提交commit,执行以下操作:
- 一个追加了索引的新的segment被写入到磁盘
- 追加了新的segment名字的提交点被写入磁盘
- 磁盘进行同步,将所有在文件系统缓存中等待的写入都刷新到磁盘
- 开启一个新的segment,其包含的文档可以被搜索
- 内存缓存被清空,等待接收新的文档
删除和更新:
segment是不可改变的,既不能把文档从旧的segment中删除,也无法通过修改旧的segment来反应文档的更新。每个提交点会包含一个*.del*文件来记录被删除文档的信息。
当一个文档被删除时,实际上只是在*.del*文件中被标记,被标记删除的文档仍然可以被查询匹配到,但是会在最终返回结果前从结果集中移除。
当一个文档被更新时,旧的文档被标记删除,新的文档被索引到一个新的segment中。查询时两者都会被查到但是旧的文档会在结果返回前被移除。
近实时搜索
提交commit一个新的segment到磁盘需要一次同步fsync操作来确保segment被持久化到磁盘上,这样在节点挂了时不会丢失数据。但是fsync操作代价很大,如果每次索引一个文档都去执行一次的话会有很大的性能问题。
通过在提交前,将缓存中的内容写入到一个内存segment中,使这些还没有被提交的文档可以被搜索,然后清空缓存等待新的文档。Lucene允许新的segment被写入和打开使其包含的文档在没有进行一次完整的提交时便对搜索可见。这种方式比进行一次提交代价要小得多,并且在不影响性能的前提下可以被频繁的执行。
通过refresh操作来写入和打开一个新的segment。默认情况下每个分片会每秒自动刷新一次,即文档的变化并不是立即对搜索可见而是在一秒后变为可见,这就是ES近实时搜索的原因。
Translog
上述过程中提到的内存segment用于在提交之前使文档可以被搜索,但是在提交之前如果节点挂掉,那么这部分数据就会丢失。所以ES增加一个transaction log,即translog来记录ES的每一次操作:
- 一个文档被索引后被添加到内存缓冲区,将操作日志记录到translog
- 通过refresh操作时内存中文档被刷新到一个新的内存segment中,清空缓存但是保留translog。这个内存segment时被打开的使其包含的文档可以被搜索
- 不断重复这个过程,更多文档添加到内存缓冲区并追加到translog中,并在一秒后refresh到内存segment中
- 随着translog变得越来越大,到达某个界限后执行flush操作:执行一次全量提交commit,创建一个新的translog:
- 所有在内存缓冲区中的文档都被写入一个新的segment中,即持久化到磁盘上,即内存segment转为物理segment
- 缓冲区被清空
- 新的提交点被写入磁盘
- 文件系统缓存通过fsync被flush
- 删除旧的translog
translog提供所有还没有被刷新到磁盘的操作的持久化记录,当ES启动时,它会从磁盘中使用最后一个提交点去恢复已知的segment,并且重新执行translog中所有在最后一次提交后发生的变更操作。
translog也被用来提供实时的CURD。当你尝试通过ID查询,更新,删除一个文档,它会尝试在从相应的segment检索之前,首先检查translog中最近所有的变更,这意味着总是可以获取到文档的最新版本。
flush操作
一次flush操作是指执行一个提交commit且截断translog。分片每30分钟会自动flush,或者在translog太大时进行flush,这些阈值可以进行配置。
segment合并
由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。
段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。
合并流程:
- 当索引的时候,refresh操作会创建新的segment并将其打开以便搜索
- 合并进程选择一小部分大小相似的段,在后台将它们合并到更大的段中。这个过程不会中断索引和搜索
- 合并结束,删除老的segment
- 新的segment被flush到了磁盘
- 写入一个包含新的segment且排除旧的和被合并的segment的新提交点
- 新的segment被打开供搜索
- 旧的segment被删除