网上搜了很多文章,基本上都是零零散散,有2个问题一直困扰着我:
1、ES为了保障性能,不实时刷盘,但是实时写入translog,难道写入translog不耗性能?
2、ES到底会不会丢数据?
先说结论:
1、es实时刷盘是由于Lucene复杂的数据结构造成的,写入translog就相当于写普通文件
2、按照默认设置,当数据变更时,translog是实时写入的,所以不会。
ElasticSearch是基于Lucene的,要了解ES,得先了解Lucene的基本用法:
这里只列关键代码:
writer.addDocument(document);//写文档
writer.commit();//只有文档提交,才会刷新到磁盘,文档可被搜索到,但是这一步很耗性能
网上引用的:https://blog.csdn.net/madman188/article/details/51241886
“commit是把所有内存缓冲区的数据写入到硬盘,是完全的硬盘操作, 重量级操作。这是因为,Lucene索引中最主要的结构posting通过VINT和delta的格式存储并紧密排列。合并时要对同一个term的posting进行归并排序,是一个读出,合并再生成的过程。如果要实现近实时搜索,Lucene提供了实时获得reader的API,这个调用将导致flush操作,生成新的segment,但不会commit(fsync),从而减少 了IO,新的segment被加入到新生成的reader里。从返回的reader里,可以看到更新。所以,只要每次新的搜索都从IndexWriter获得一个新的reader,就可以搜索到最新的内容。这一操作的开销仅仅是flush,相对commit来说,开销很小。Lucene的index组织方式为一个index目录下的多个segment。新的doc会加入新的segment里,这些新的小segment每隔一段时间就合并起来。因为合并,总的segment数量保持的较小,总体search速度仍然很快。为了防止读写冲突,lucene只创建新的segment,并在任何active的reader不在使用后删除掉老的segment。
要实现这个flush,就是使用 SearcherManager,具体用法可参考:
https://www.jianshu.com/p/d3194ace2992
而ES正是这么做的:
#①写入数据
调用writer.addDocument()写文档,代码( IndexShard.index())
②刷新
每秒刷新内存中的数据到文件系统缓存,此时并未写到磁盘,但是已是打开可供查询的状态,具体代码( IndexService的内部类: AsyncRefreshTask)
1s钟是可以设置的,具体方法:
PUT my_index
{
"settings": {
"refresh_interval": "1m"
}
}
也可以手动执行刷新:
POST /_refresh
POST /blogs/_refresh
refresh_interval 可以在既存索引上进行动态更新。 在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来:
PUT /my_logs/_settings
{ "refresh_interval": -1 }
官方提示:
尽管刷新是比提交轻量很多的操作,它还是会有性能开销。 当写测试的时候, 手动刷新很有用,但是不要在生产环境下每次索引一个文档都去手动刷新。 相反,你的应用需要意识到 Elasticsearch 的近实时的性质,并接受它的不足。
③写入translog
此时有一个问题,数据虽然可被搜索到,但是数据没有被提交到磁盘,数据仍然可能会丢失,所以ES引入了transLog,事务日志,当写入数据时,同时写入transLog,代码( InternalEngine.index())
translog默认情况下是 是每 5 秒被 fsync 刷新到硬盘, 或者在每次写请求完成之后执行(e.g. index, delete, update, bulk), 也就是说默认情况下,在没有写入translog前,你的客户端不会得到一个 200 OK 响应。通过tranLog保障数据不会丢失。代码可见( AsyncAfterWriteAction.run())
④ 在不断的写入,translog主键变大时,es会执行 一个全量提交,即Lucene的写入磁盘
这个执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush 。 分片每30分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。
flush API 可以 被用来执行一个手工的刷新(flush):
刷新(flush) blogs 索引。
POST /blogs/_flush
刷新(flush)所有的索引并且并且等待所有刷新在返回前完成。
POST /_flush?wait_for_ongoing
你很少需要自己手动执行 flush 操作;通常情况下,自动刷新就足够了。
这就是说,在重启节点或关闭索引之前执行 flush 有益于你的索引。当 Elasticsearch 尝试恢复或重新打开一个索引, 它需要重放 translog 中所有的操作,所以如果日志越短,恢复越快。
官方文档:
借助ElasticSearch官方文档的说明,我大致总结了下几个流程:
一、初始状态:一个 Lucene 索引包含一个提交点和三个段
Elasticsearch 基于 Lucene, 这个 java 库引入了 按段搜索 的概念。 每一 段 本身都是一个倒排索引, 但 索引 在 Lucene 中除表示所有 段 的集合外, 还增加了 提交点 的概念 — 一个列出了所有已知段的文件
二、 新的文档被添加到内存缓冲区并且被追加到了事务日志
一个文档被索引之后,就会被添加到内存缓冲区,并且 追加到了 translog ,此时translog已经写入文件,也就是数据不会丢失,但是此时文档还不可被搜索
三、Refresh,文档可被搜索
定时器每隔1s刷新,刷新(refresh)将完成下面几个动作,
* 这些在内存缓冲区的文档被写入到一个新的段中,但还没有提交到磁盘。
* 这个段被打开,使其可被搜索。
* 内存缓冲区被清空。
四、事务日志不断积累文档
这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志
五、Commit
每隔一段时间–例如 translog 变得越来越大–索引被刷新(flush);一个新的 translog 被创建,并且一个全量提交被执行,在commit之后,段被全量提交,并且事务日志将被清空
* 所有在内存缓冲区的文档都被写入一个新的段。
* 缓冲区被清空。
* 一个提交点被写入硬盘。
* 文件系统缓存通过 fsync 被刷新(flush)。
* 老的 translog 被删除。