为什么ES的搜索是近实时的?ES如何保证更新的持久化?这是本篇博客的核心内容
为了保证文档可以被搜索到,ES采用了倒排索引的模式,详细的原理请参照:ES原理
写入磁盘的倒排索引是不可变的,它的优缺点
优点:
1、不需要锁,因为不可变就没有更新。
2、一但放入内存中,就不需要更新。同时意味着需要有足够的内存空间。
3、 写入单个大的倒排索引,可以进行数据压缩,减少磁盘IO和内存占用。
缺点:
1、不可变意味着新增和修改文档内容,需要重建整个索引,频繁的重建会引起大量的消耗(IO、CPU)。
解决第一步中,使用倒排索引的好处,同时可以动态更新索引,采用的方案是使用多个索引。
per-segment search概念:
一个段(Segment)是有完整功能的索引。Lucene中是包含段的集合+提交点(commit point)。
一个 per-segment search 如下工作:
1、新的文档首先写入内存区的索引缓存
2、缓存中的内容不时被提交:
1)一个新的段(额外的倒排索引)写入磁盘。
2)新的提交点写入磁盘,包括新段的名称
3)所有写操作等待文件系统缓存同步到磁盘,确保被写入。
3、新的段被打开,它包含的文档可以被检索
4、内存中的缓存被清除,等待接受新的文档。
问题:新的段被加入到索引中,但是旧的段还存在,如何在检索的时候检索新的段?
段是不可变的,所以文档不能从旧的段中删除,旧的段也不能进行更新,所以每个提交点(commit point)包含了段上一个.del文件,里面为段上被删除的文档。
被删除的文档和北更新的文档,旧文档被标记为删除,但依然可以匹配查询,但最终的返回之前会被从结果中删除。
因为第二部分动态中的pre-segment serach机制,新增加的文档在没有落到磁盘之前是不可检索的,所以新增加的文档是需要延迟一段时间才可以被搜索到,磁盘会是瓶颈(fsync是昂贵的,不能在每个文档被索引时就触发)。
位于内存和磁盘之间的是文件系统缓存。在内存索引缓存中的文档被写入新的段,但是新的段首先写入文件系统缓存(高效)。但是一旦一个文件被缓存,它可以被打开和读取(这里是否应该是需要考虑锁的问题?)。
这种写入打开一个新段的轻量级过程,叫做refresh,默认ES的配置(refresh_interval 配置)是每秒钟刷新一次,这也就提供了ES近实时搜索的功能。虽然这种轻量级的提交消耗较小,但是频繁的调用对系统资源消耗也很大,所以如果对搜索的实时性要求不那么高,可以把刷新频率调低一些。
问题:为了减少消耗,提升性能,我们引用了文件系统缓存但是没有fsync同步文件到磁盘,如果出现异常情况,数据的安全性无法保证。
ES增加了事务日志(transLog),他与mysql的事务提交日志原理非常像,这里用来记录每次操作,持久化的过程如下:
1、一个新文档被加入索引,同时写入事务日志(translog insert)
2、refresh 的操作:
1)内存缓冲区的文档写到段中,存储于文件系统缓存,但是不进行fsync。
2)段被打开,使文档可以被搜索,然后清除缓存
3)不处理translog中的内容。
3、随着文档的新增和提交,translog的内容会逐步增加,当日志到达一定量级以后,会进行一次全提交:
1)内存缓存区中的所有文档会写到新的段中。
2)清除缓存
3)一个提交点写入硬盘
4)文件系统缓存通过fsync操作flush导硬盘
5)清除事务日志translog。
4、当故障重启后,ES会用最近一次的提交点从硬盘中恢复一直的段,并且从translog中恢复所有操作。
5、同时事务日志还来提供实时的CRUD操作,当尝试用ID进行CRUD时,它会先查日志最新的改动,来保证获取到文档的最新版本。
进行一次提交并删除事务日志的操作叫做 flush 。分片每30分钟,或事务日志过大 会进行一次flush操作。如果没有必要一般不要人工进行flush操作。
通过每秒自动刷新创建的段,用不了多久段的数量就爆炸了,有太多的段是一个需要解决的问题(占用资源,每次请求都要检查每个段)。
ES通过段合并解决问题,小段合并成大段,然后再合并成更大的段:
(这里有个疑问待确认?合并段也有flush操作,与translog中的落盘操作是如何区分的)
1、索引过程中,refresh会创建新的段,并打开它
2、合并过程会在后台选择一些小的段(包含已提交和未提交的)合并成大的段,这个过程不会中断索引和搜索
3、合并后的段会被flush到硬盘,新的提交点会写入新的段,排除旧的段。新的段打开提供搜索,旧段被删除
合并段会消耗大量的IO和CPU,ES会限制合并的过程,保证有足够的资源来进行搜索。