倒排索引组成结构以及索引不可变原因
对于倒排索引是非常适合用来进行搜索的
它的结构:
(1)包含这个关键词的 document list
(2)包含这个关键词的所有 document 的数量:IDF(inverse document frequency)
(3)这个关键词在每个 document 中出现的次数:TF(term frequency)
(4)这个关键词在这个 document 中的次序
(5)每个 document 的长度:length norm
(6)包含这个关键词的所有 document 的平均长度
其实本质上主要是为了计算相关度分数
_score = boost * idf * tf
此时 boost = 2.2, idf = 0.18232156, tf = 0.5116279
idf = log(1 + (N - n + 0.5) / (n + 0.5))
此时 n = 2 (n, number of documents containing term),
N = 2(N, total number of documents with field)
tf = freq / (freq + k1 * (1 - b + b * dl / avgdl))
此时 freq = 1(freq, occurrences of term within document),
k1 = 1.2(k1, term saturation parameter),
b = 0.75(b, length normalization parameter),
d1 = 4 (dl, length of field),
avgdl = 5.5(avgdl, average length of field)
倒排索引的不可变好处
(1)不需要锁,提升了并发能力,避免锁的问题
(2)数据不变,一直保存在 OS cache 中,只要 cache 内存足够
(3)filter cache 一直驻留在内存
(4)可以压缩,节省 cpu 和 io 开销
这个对应的就是 primary shard 的数量不变,不能修改 field 的属性(将 date 改成 text)
倒排索引不可变的坏处
(1)每次都需要重新构建整个索引
document 写入原理
(1)数据写入 buffer
(2)commit point
(3)buffer 中的数据写入新的 index segment
(4)等待在 OS cache 中的 index segment 被 fsync 强制刷到磁盘上
(5)新的 index segment 被打开,供 search 使用
(6)buffer 被清空
下面做几点说明:
1、在 Elasticsearch 中,底层用的是 lucene,lucene 底层的 index 是分为多个 segment 的,每个 segment 都会存放部分数据。
2、如果是删除操作,每次 commit 的时候,就会生成一个.del 文件,标明哪个 index segment 的哪个 document 被删除了。
3、如果是更新操作,实际上是将的 doc 标记为 deleted,然后将新的 document 写入新的 index segment 中。下次 search 过来的时候,也许会匹配到一个 document 的多个版本,但是之前的版本已经被标记为 deleted 了,所以只会返回最新版本的 doc
4、如果搜索请求过来,在 index segment 中,匹配到了 id= 1 的 doc,此时会发现在.del 文件中已经被标识为 deleted 了,这种数据就会被过滤掉,不会作为搜索结果返回。
图示如下:
写入流程近实时 NRT
现有的流程的问题,每次都必须等待 fsync 将 segment 刷入磁盘,才能将 segment 打开供 search 使用,这样的话,从一个 document 写入,到它可以被搜索,可能会超过 1 分钟,这也就不是近实时的搜索了。主要的瓶颈在于 fsync 从磁盘 IO 写数据进磁盘是很耗时的。
ES 写入流程的改进:
(1)数据写入 buffer
(2)每个一定时间,buffer 中的数据被写入 segment 文件,但是先写入 OS cache
(3)只要 segment 写入 OS cache,那就直接打开供 search 使用,不立即执行 commit
数据写入 OS cache,并被打开供搜索的过程,叫做 refresh,默认是每隔一秒 refresh 一次,也就是说,每隔一秒就会将 buffer 中的数据写入 OS cache 中,写入一个新的 index segment file。所以 ES 是近实时的,数据写入到可以被搜索,默认是 1 秒。
一般不用修改,让 ES 自己搞定就好了,要修改的话,通过 refresh_interval 参数即可
格式:
PUT /{index}
{
"settings": {"refresh_interval": "1s"}
}
图示如下:
写入流程的实现 durability 可靠存储
最终流程:
(1)数据写入 buffer 缓存和 translog 日志文件(2)每隔一秒,buffer 中的数据被写入新的 segment file,并进入 os cache,此时 segment 被打开并供 search 使用(3)buffer 被清空(4)重复 1~3,新的 segment 不断添加,buffer 不断被清空,而 translog 中的数据不断累加(5)当 translog 长度达到一定程度的时候,commit 操作发生(5.1)buffer 中的所有数据,写入一个新的 segment,并写入 os cache,打开供使用(5.2)buffer 被清空(5.3)一个 commit point 被写入磁盘,标明了所有的 index segment(5.4)filesystem cache 中的所有 index segment file 缓存数据,被 fsync 强制刷到磁盘上(5.5)现有的 translog 被清空,创建一个新的 translog
translog
对 Lucene 的更改仅在 Lucene 提交期间持久保存到磁盘,这是一项相对昂贵的操作,因此无法在每次索引或删除操作后执行。如果进程退出或硬件发生故障,Lucene 将在一次提交之后和另一次提交之前发生的更改将从索引中删除。
因为 Lucene 提交对于每个单独的更改都执行起来太昂贵,所以每个分片副本还有一个事务日志,称为与之关联的 translog。所有索引和删除操作在由内部 Lucene 索引处理之后但在确认之前写入 translog。在发生崩溃的情况下,最新的已确认但尚未包含在上一个 Lucene 提交中的事务可以在分片恢复时从 translog 中恢复。
Elasticsearch flush 是执行 Lucene 提交并启动新的 translog 的过程。在后台自动执行刷新以确保 translog 不会变得太大,这将使得在恢复期间重放其操作需要相当长的时间。手动执行刷新的能力也通过 API 公开,尽管很少需要。
translog 设置
translog 中的数据仅在 fsync 编辑和提交 translog 时持久保存到磁盘。如果发生硬件故障,自上次 translog 提交以来写入的任何数据都将丢失。
默认情况下,fsync 如果 index.translog.durability 设置为 async 或 request 在每个索引,删除,更新或 批量请求结束时设置为(默认),则 Elasticsearch 会每隔 5 秒提交一次 translog。更确切地说,如果设置为 request,则 fsync 在主数据库和每个已分配的副本服务器上成功编辑和提交 translog 之后,Elasticsearch 将仅向客户端报告索引,删除,更新或批量请求的成功。
以下可动态更新的每索引设置控制 translog 的行为:
index.translog.sync_interval
fsync 无论写入操作 如何,translog 都经常被写入磁盘并提交。默认为 5s。小于的值 100ms 是不允许的。index.translog.durability
是否 fsync 在每个索引,删除,更新或批量请求之后提交 translog。此设置接受以下参数:request(默认)fsync 并在每个请求后提交。如果发生硬件故障,所有已确认的写入都已提交到磁盘。async
fsync 并在每个背景中提交 sync_interval。如果发生硬件故障,将丢弃自上次自动提交以来的所有已确认写入。index.translog.flush_threshold_size
translog 存储尚未安全保存在 Lucene 中的所有操作(即,不是 Lucene 提交点的一部分)。尽管这些操作可用于
读取,但如果要关闭并且必须恢复,则需要重新编制索引。此设置控制这些操作的最大总大小,以防止恢复时间
过长。达到最大大小后,将发生刷新,生成新的 Lucene 提交点。默认为 512mb。index.translog.retention.size
要保留的 translog 文件的总大小。保留更多的 translog 文件会增加在恢复副本时执行基于同步操作的机会。如
果 translog 文件不足,副本恢复将回退到基于文件的同步。默认为 512mb
index.translog.retention.age
保留 translog 文件的最长持续时间。默认为 12h。
图示如下:
数据恢复
假设 os cache 中囤积了一些数据,但是此时不巧,宕机了,os cache 中的数据全部丢失,那么我们怎么进行数据恢复呢?
我们知道写 doc 的时候也会写入 translog,那么 translog 就存储了上一次 flush 直到现在最近的数据变更记录。机器被重启之后,disk 上的数据并没有丢失,此时就会将 translog 文件中的变更记录进行回收,重新执行之前的各种操作,在 buffer 中执行,在重新刷一个一个的 segment 到 os cache 中,等待下一次 commit 发生即可。
海量磁盘文件的合并
每秒一个 segment file,会导致文件过多,而且每次 search 都要搜索所有的 segment,很耗时。所以在 Elasticsearch 内部会默认在后台执行 segment merge 操作 (forcemerge),在 merge 的时候,被标记为 deleted 的 document 也会被彻底物理删除掉。
每次 merge 的操作流程:
(1)选择一些有相似大小的 segment,merge 成一个大的 segment
(2)将新的 segment flush 到磁盘上去
(3)写一个新的 commit point,包括了新的 segment, 并且排除旧的那些 segment
(4)将新的 segment 打开供搜索
(5)将旧的 segment 删除