document写入原理
在es底层,用的是Lucene,Lucene底层的index是分为多个segment的,每个segment都会存放部分数据
图中,客户端写入一个document的时候:
- 先写到了操作系统中的buffer缓存中
- 然后进行commit point
- buffer中的数据写入了新的index segment
- 然后写入操作系统的缓存中
- 缓存中的index segment被fsync强制刷新到磁盘上
- 同时新的index segment被打开,供搜索使用
- 将buffer缓存清空
更新删除原理
如果是更新操作,实际上是将现有的document标记为deleted,然后将新的document写入新的index segment中,下次search过来的时候,也许会匹配到一个document的多个版本,但是之前的版本已经被标记为deleted了,所以会被过滤掉,不会作为搜索结果返回,删除操作同理.
每次commit point时,会有一个.del文件,标记了哪些segment中的哪些document被标记为deleted了
搜索的时候回依次查询所有的segment,从旧的到新的,比如被修改过的document,在旧的segment中,会被标记为deleted,在新的segment中会有其新的数据
问题
如果按照上面的流程的话,每次都必须等待fsync将segment刷入磁盘,才能将segment打开供search使用,这样的话,从一个document写入,到它可以被搜索,可能会超过1分钟,这就不是近实时的搜索了, 主要瓶颈在于fsync实际发生磁盘IO写数据进磁盘是很耗时的.
流程改进
- 数据写入buffer中
- 每隔一定的时间(默认是1s),buffer中的数据被写入新的segment文件,然后写入os cache中
- 只要segment写入到了os cache中了,那就直接打开index segment 供使用,不立即commit.
- 最后把buffer清空
数据写入os cache并被打开供搜索的过程,叫做refresh,默认是每隔1s refresh一次,就是说每隔一秒,就会将buffer中的数据写入一个新的index segment文件,先写入os cache中.所以es是近实时的,数据写入到可以搜索,默认是1秒.
我们也可以手动去设置refresh的间隔时间,比如时效性要求较低,写入数据一分钟后被搜索到就可以了1
2
3
4
5
6PUT /my_index
{
"settings": {
"refresh_interval": "60s"
}
}
问题
数据不及时写入到磁盘中,而是在缓存中,如果宕机的话,数据就会丢失,就不可靠了
再次优化写入流程
- 写入document的时候,数据同时写入buffer缓冲和translog日志文件
- 每隔1秒中,buffer中的数据被写入新的segment file,并进入os cache中,此时segment被打开并供search使用
- buffer被清空
- 重复1-3,新的segment不断添加,buffer不断被清空,而translog中的数据不断累加
- 当translog长度达到一定的程度的时候,commit操作发生.
5.1. buffer中所有数据写入一个新的segment中,并写入os cache 打开供使用.
5.2. buffer被清空
5.3. 一个commit point被写入磁盘,标明了所有的index segment
5.4. os cache中的所有 数据被fsync强行刷到磁盘上去
5.5. 现有的translog被清空,创建一个新的translog
基于translog和commit point进行数据恢复
磁盘上存储的是上次commit point为止,所有的segment file,那么translog中存储的就是上一次flush(commit point)知道现在最近的数据变更记录
如果说 os cache中已经囤积了一些数据,没有被刷到磁盘上,这个时候宕机了, 这时候机器重启,此时会将translog文件中的变更记录进行回放,重新执行之前的各种操作,等待下次commit即可
每次flush 会自动清空translog,默认每隔30分钟flush一次,或者当translog过大的时候,也会flush.
我们也可以手动flush,POST /my_index/_flush,一般来说别手动flush,让它自动执行就可以了
translog,也是先放在缓存中的,每隔5秒被fsync一次到磁盘上.一般是在一次增删改操作之后.
如果说在一次增删改操作的时候正好要fsync translog到磁盘上,那么会等待primary shard和replica shard都成功之后,这次增删改操作才会成功
但是这种在一次增删改时强行fsync translog可能会导致部分操作比较耗时
如果可以允许部分数据丢失,可以设置异步fsync translog
1 | PUT /my_index/_settings |
终极优化
上面说的,每秒生成一个segment文件,文件会越来越多,而且每次search都要搜索所有的segment,很耗时
es会默认在后台执行合并的操作,在merge的时候,被标记为deleted的document 也会被彻底物理删除
每次merge的流程是
- 选择一些有相似大小的segment,merge成一个大的segment
- 将新的segment flush到磁盘上去
- 写一个新的commit point,包括了新的segment,并排除旧的那些segment
- 将新的segment打开供搜索
- 将旧的segment删除
也可以通过 POST /my_index/_optimize?max_num_segments=1 来手动合并,但是尽量不要手动执行