读取流程
??es是一个分布式的存储和检索系统,在存储的时候默认是根据每条记录的_id字段做路由分发的,ES会把index中的多个document分配到多个不同的分片上,且尽量保持每个分片上document的数量平衡。es服务端是准确知道每个document分布在哪个分片(shard)上的。一个document相当于关系型数据库中的一行数据,它在ES中是最小的数据单位,因此一个document不会被拆分到多个分片上。 ??当我们读取数据时候,我们不知道那些document会被匹配到,任何一个分片上都有可能,所以一个查询请求必须查询索引的一个或多个分片才能完整的查询到我们想要的结果。找到所有匹配的结果是查询的第一步,来自多个分片上的数据集在分页返回到客户端之前会被合并到一个排序后的list列表,由于需要经过一步取top N的操作,所以读取需要进过两个阶段才能完成,分别是query和fetch。下面我们通过一个简单的举例来形象的说明。
读阶段(query)
??当发出一个查询请求的时候,这个查询会被广播到索引里面的每一个分片(主分片或副本分片),每个分片会在本地执行查询请求后会生成一个命中文档的优先级队列。这个队列是一个排序好的top N数据的列表,它的size等于from+size的和,也就是说如果你的from是10,size是10,那么这个队列的size就是20,所以这也是为什么深度分页不能用from+size这种方式,因为from越大,性能就越低。假如有1万条数据记录了各种水果在全国各省市的,其中草莓的销量数据如下:
??它们被分到了不同的分片上,当用户想分页查询草莓销量的Top2数据的第二页时,(Select * from table where 品种=‘草莓’ order by 销量 desc limit 2,2 ),客户端收到用户发出的请求,将它发送到服务端的随机一个节点A上,此时这个A节点就成了协调节点,A节点会创建一个优先级队列,筛除A节点的前4条数据,同时会把这个请求转发到index的每一个主分片或者副本分片上,每个节点会在本地查询,然后添加结果到本地的排序好的优先级队列里面;就拿上面的这个草莓的例子来讲,三个节点各返回了前四条数据,这样加起来就是12条数据。注意此处为什么是返回4条数据:实际我们想得到的结果是草莓销量50000和36000,如果每个节点排倒叙只取第3~4条的话,则变成了7000和6000,所以每个节点要返回from+size大小数量的数据再统一排序取TopN。 这也正是ES不支持使用From+Size来进行深度分页的原因,(ES查询from+size>10000则不支持)from+size越大,那么每个分片都要返回大量数据给coordinate node协调节点,会消耗大量的带宽,内存,CPU。因此ES推荐使用滚屏查询来进行深度分页。
??读完后每个分片返回数据ID和所有参与排序字段的值例如_score到优先级队列里面,然后再返回给coordinating node(协调节点,这里面是A节点,即请求第一个打到的节点),然后A节点负责将所有分片里面的数据给合并到一个全局的排序的列表。 上面提到一个术语叫coordinating node,这个节点是当search请求随机负载的发送到一个节点上,然后这个节点就会成为一个coordinating node,它的职责是广播search请求到所有相关的分片上,然后合并他们的响应结果到一个全局的排序列表中然后进行第二个fetch阶段,注意这个结果集仅仅包含docId和所有排序字段的值,search请求可以被主分片或者副本分片处理,这也是为什么我们说增加副本的个数就能增加搜索吞吐量的原因,coordinating节点将会通过round-robin的方式自动负载均衡。
取阶段(fetch)
??上面我们讲到当每个节点前4条数据的ID和销量都汇总到了协调节点A之后,A就会再对这12条数据再进行排序,然后取出第3~4条的ID,再根据ID发送MultiGet请求到相关分片上得到对应的doucument返回给客户端。
SearchType
在查询时ES有多种搜索的方式,分别是query then fetch、query and fetch、DFS query and fetch、DFS query then fetch,文章上面讲到的是ES默认的搜索方式query then fetch。
query then fetch(默认搜索方式)
??搜索时,没有指定搜索方式,就是使用的这种搜索方式。这种搜索方式两个步骤,第一步,先向所有的分片发出请求,各分片只返回排序和排名相关的信息(注意,不包括文档document),然后按照各分片返回的分数进行重新排序和排名,取前size个文档。然后进行第二步,去相关的分片根据ID取document。这种方式返回的document与用户要求的size是相等的。
query and fetch
??向索引的所有分片(shard)都发出查询请求,各分片返回的时候把元素文档(document)和计算后的排名信息一起返回。这种搜索方式是最快的。因为相比下面的几种搜索方式,这种查询方法只需要去每个分片查询一次。但是各个分片返回的结果的数量之和可能是用户要求的size的n倍,因为每个节点都是返回的from+size的条数。
DFS query then fetch
??这种搜索方式的流程是在上面query then fetch的基础上,在执行query前先进行了初始化散发的工作。官网文档的意思大概是初始化散发其实就是在进行真正的查询之前,先把各个分片的词频率和文档频率收集一下,然后进行词搜索的时候,各分片依据全局的词频率和文档频率进行搜索和排名。这两种方式搜索精度最高但是效率是最慢的。
写入流程
ES的文件结构
??在了解写入流程前先来看下ES的结构是什么样子。这是网上介绍ES的一张图片,从中可以看出一个ES集群中有多个Server节点,每个Server节点中含有多个Index。 ??一个Index含有多个分片(shard),其中有一个 primary 主分片,负责写入,其他副本为 replica,不能写,只能同步 primary 的数据,但可以处理读请求。 ??ES 收到写请求后,会将请求路由到index的主分片上。 ??ES的底层是Lucene引擎,ES的每一个分片对应到底层实际上就是一个 Lucene Index,包含多个 segment 文件,和一个 commit point 文件。segment 文件存储的就是一个个的 Document,commit point 相当于一个索引文件,记录了都有哪些 segment 文件。
写入流程
??从上面的ES结构我们大概能了解到了 primary shard 收到写请求,就是把 Document 数据写入 segment,如果每次写操作都是直接落盘的话,I/O 效率会比较低。所以,ES 使用了一个内存缓冲区 Buffer,先把要写入的数据放进 buffer。内存性能好,但不安全,会丢数据,所以 es 使用了一个日志文件 Translog。就像 MySQL 的 Binlog,记录着每一条操作日志,如果 ES 出现故障,重启之后可以从 Translog 中恢复数据。因为日志文件只是单纯的做内容追加,没有其他逻辑操作,所以写入速度很快。所以数据来到 primary shard 之后,先是进入 buffer,并把操作记录写入 Translog。 ??ES 默认每隔一秒执行一次 refresh 操作,会创建一个 Segment 文件,将 buffer 中的数据写入这个 segment,并清空 buffer。ES 有一个后台程序,用于 merge 合并这些 segment 文件,把小 segment 整合到一个大的 segment 中,并修改 commit point 的 segment 记录。merge 过程还会清理被删除的数据。es 接收到删数据请求时,不会真的到 segment 中把数据删了,而是把要删除的数据写到 ‘.del’ 文件中,在读操作时,会根据这个文件进行过滤。merge 合并时才真正删除,合并后的 segment 中就没有已经删除的数据了。 进入 segment 的数据就进入了 Lucene,建立好了倒排索引,可以被搜索到。经常会有面试题考ES的refresh和flush的区别,下面我们会继续介绍flush操作。 ??ES 虽然把数据写入了 segment 文件,但实际上还没有真正落盘,因为操作系统的文件系统也是有缓存的,这是操作系统层面的性能优化,ES每隔5秒会异步执行一次fsync操作,即把系统缓存的内容真正强制写到硬盘上。
??刚才讲到每次写buffer时会同时追加TransLog日志,那随着数据的写入,这个日志文件肯定会越来越大,触发清理TransLog文件的两个条件是文件大小达到阈值、或时间超过30分钟,二者满足任意一个就会执行一次flush操作。flush操作的执行共有4个步骤: ????1、执行 refresh 操作。 ????2、把这次提交动作之前所有没有落盘的 segment 强制fsync,确保写入物理文件。 ????3、创建一个提交点,记录这次提交对应的所有 segment,写入 commit point 文件。 ????4、清空 Translog,因为 Segment 都已经踏实落地了,之前的 Translog 就不需要了。 下图就展示了整个的写入流程:
参考内容: https://blog.51cto.com/u_7117633/2866130 http://t.csdn.cn/4SJax http://t.csdn.cn/j8fAL
|