就是ES在refresh的时候写缓存的问题。
这视乎是老话题了,但有种不吐不快的感角
各种网上资料,B战上大佬的视频,都是给你灌输这个理念:
ES在refresh时,内存中的数据是先写到缓存
(这个缓存叫啥, System cache,OS cache, Filesystem cache,五花八门),
然后从缓存中读出新段,因为此刻不需要写磁盘,所以比较快,这就实现了NRT。
而缓存里的数据会在flush时写到磁盘上
听起来很heli,但这是真相吗?
下面解释:为什么我认为这是个极其荒唐的说法。
(如果有任何怀疑或错误之处,请指出)
对任何一句话有异议,我会告诉你具体实现代码在哪
我的偶像们镇楼:
这视乎是老话题了,但有种不吐不快的感角
各种网上资料,B战上大佬的视频,都是给你灌输这个理念:
ES在refresh时,内存中的数据是先写到缓存
(这个缓存叫啥, System cache,OS cache, Filesystem cache,五花八门),
然后从缓存中读出新段,因为此刻不需要写磁盘,所以比较快,这就实现了NRT。
而缓存里的数据会在flush时写到磁盘上
听起来很heli,但这是真相吗?
下面解释:为什么我认为这是个极其荒唐的说法。
(如果有任何怀疑或错误之处,请指出)
对任何一句话有异议,我会告诉你具体实现代码在哪
我的偶像们镇楼:
12 个回复
Charele - Cisco4321
赞同来自:
ES执行refresh的时候,会执行到这里。
图中这行代码会写内存中各种内容到新段文件(doc, pos, tim, tip,fnm等等),
就是通过标准的Java API, write(), flush()/close()来写文件,
你说不对,写的时候会有缓存,
就是什么“内核缓冲区高速缓存”之类的东东。
不否认OS层面会有缓存,但那是底层做的事。
它用的是标准的写文件API,它并没有用某种“写缓存API”(也不存在)。
它和你在别的地方看到的Java写一个文件,并没有何任区别!
如果非得跟缓存拉上关系,那照这个说法,
Java中所有写文件操作就都是在写缓存了?(也就是没有“不是写缓存”的了)
既然大家没区别,那为什么得特别强调说这时,ES refresh是在“写缓存(从缓存读)”呢?
我认为ES refresh操作,就是写磁盘文件,
并不是“写缓存"(可能会有某种缓存会参与,但本质上是写文件)
当这行代码完成后,新的段文件就写到了磁盘上,
然后把新段从磁盘上读出来,
这里会有个观点,说数据可能还在缓存,也可能已经写到了磁盘,要看系统繁忙程度而定,
意思就是说:如果在缓存就从缓存读,在磁盘就从磁盘读,不确定。
我的看法是:在读的时候,肯定在磁盘上。因为它就是从磁盘读的
(新段 + 老段) ---> dr ---> 返回给你
既然是普通的写/读文件,好像就体现不出NRT“快”的优势了,
大佬博客里有说明:
Charele - Cisco4321
赞同来自:
NRT,实际就是会复用原有段引用,节省了开销,所以快。
相比非NRT方式而言,因为那种方式要(从头)打开所有的段才能读到数据
Charele - Cisco4321
赞同来自:
为什么呢。
看这里:(这个链接来自Lucene源码注释)
https://blog.thetaphi.de/2012/ ... .html
这是关于mmap的文章(细节可以自己看)
mmap里确实会有filesystem cache这些概念。
mmap那套牛B高效的机制,只是用来读的。
在写上的,是没有什么magic的。
Charele - Cisco4321
赞同来自:
Charele - Cisco4321
赞同来自:
比如ES refresh完成后,会不会传一个(writer里面的)缓存的指针给reader,
reader就不需要从磁盘读了,而从某个缓存读?
跟本没这么回事。reader接受的就是一个文件名,
它只能老老实实地从磁盘打开这个文件,,,
面且writer和reader之间也没有类似的notify机制,
就是说“我写完了,立马通知你,你赶紧读,趁数据还在缓存里,晚了你就得读磁盘了”
writer和reader完全是两个不相关的过程。
拿doc文件举例
请问:当你第一次去读一个新文件时,这个文件能在某个缓存里吗?
如果在缓存里,那说明别人已经读过了,你就不是第一次读了
leohe777
赞同来自:
Charele - Cisco4321
赞同来自:
分工很明确,我管不了你Lucene怎么做。
我ES refresh了,我就得可见。
你如何实现,数据在哪我不管,
我ES这里自己写日志来保证我(来不及flush时)的安全。
我ES flush了,我就认为数据安全了(就可以丢弃日志了)。
ES refresh = Lucene flush, ES flush = Lucene commit
注意下单词在不同场合的区分
我先从Lucene的角度来说,
比如现你在磁盘上有段1,和提交文件Segment_A(这个里面记录它生成时所有的段)
你现在加了新文档,(且执行flush,生成了新段2)
这个时候,如果用NRT Reader,尽管没有生成新的提交文件,但它是可以直接读段2的。
(就是ES中refresh后就能查到数据)
执行commit()时,它会写新的Segment_B文件(里面包括段1和段2)
好,你重新打开程序时,它会读最后的Segment_B,就可以读出段1和段2的数据
(这时它并不能直接读段文件,而是要通过段提交文件来识别有哪些段)
如果你只是flush了,就是段2的文件写到了磁盘上,
但没有commit,没有生成Segment_B提交文件。
如果程序挂了,重开,只能去读Segment_A(里面只记录了段1),
所以尽管段2文件在磁盘上,但你无法获取到,等于段2就丢失了。
Lucene commit的意义在就在这里,生成提交点文件。
(它还会fsync来确保各个文件的安全)
=======================================================================
全局检查点:简称gcp
回到ES,你说的“tanslog 5s才刷一次盘”,这是不准确的。
ES中有两种模式,一种是Request,这种是每次插入一个文档,都会把日志刷盘
(这个工作叫“同步gcp”,除了刷日志,它还会写最新的gcp)
另一种Async时,的确是缺省5秒一次执行“同步gcp”
1 Request方式时,你插入新文档(它会马上刷日志,生成最新的gcp),
(且refresh,1秒一次,磁盘上生成了新段),
如果你没有flush,ES挂了。重启后,
记录的gcp来判断你应该有哪些数据(缺哪些数据),
然后它从日志中找出来,replay一下,重新插入这个文档(等于挂之前生成的那些段文件就作废了)
如果你执行了flush操作
(ES flush 也会写段文件,如果你禁用了refresh,它会在这里写),
最新gcp里所需要的段文件和Segment提交文件同时都在磁盘上,日志也就没用了
(旧日志并不是简单的认为没用)
2 Async方式,你插入了文档(同时refresh生成了新段)。
如果你没有来得及等5秒执行“写最新gcp”和刷日志。
挂了重启后,你还是读老的gcp,读出老的提交文件,读出老的段。
(因为老的gcp并不要求你这个新文档,所以不会去找日志,也没日志)
尽管磁盘上有新段文件,等于不存在。等于这个文档就丢失了
像CCR,peer恢复这种所需的数据,都是由租约去保证的
租约可能会引用到老日志文件,通过“最小保留SeqNo点”来引用。只有小于这个点的日志才能被删除。
所以“副本恢复和ccr使用软删除都可以不需要translog了”这是不对的
Charele - Cisco4321
赞同来自:
这里ES flush后,执行的“删除日志”的操作,它叫“清除未参考的”,
它有一系列判断,
就是说,并不是flush之后,就能删除旧日志
人们常说的“flush操作后会删除旧日志“也是不对的
leohe777
赞同来自:
emmning - for learn you know
赞同来自:
Charele - Cisco4321
赞同来自:
你的意思,数据会一直呆在缓存里,直到commit时才会实际写入磁盘,是吗?
如果你是这个意思,你可以去试啊
3种方式,不管哪都可以。
1 Lucene只flush,不commit,退出2 Lucene,用NRT Reader查一下数据,就退出
3 ES,只refresh(不管自动还是手动),不执行flush,杀死ES进程
(2和3,本质上是是一样的操作)
A 看看这3种情况下有没有数据文件
B 看看这3种情况下和正常情况生成的数据文件是不是一模一样
zhous
赞同来自: