当前位置: 首页 > 后端技术 > Java

ES索引操作

时间:2023-04-02 00:47:15 Java

本文主要有以下内容:文档索引创建ES中的Translog并发索引更新操作并发索引检索查询三阶段文档索引步骤客户端向node1发送新建、查询或删除请求。node根据文档的_id判断文档属于shard0,请求会转发给node3,因为shard0的primaryshard当前分配在node3上。node3在primaryshard上执行请求,如果成功,会将请求并行转换到node1和node2的replica分片。一旦所有的副本分片都报告成功,node3就会向协调节点报告成功,协调节点再向客户端报告成功。以上操作是基于单个文档的。多个文档的批量操作和上面的过程类似,这里就不多说了。详细解释了文档索引过程的总体流程图。coordinator节点默认使用文档ID参与计算(路由也支持),为路由提供合适的分片。shard=hash(document_id)%(num_of_primary_shards)当shard所在节点收到协调节点的请求时,会将请求写入内存缓冲区,然后写入文件系统缓存(操作系统文件缓存)每隔一定时间(默认1秒,此处不由JVM管理),从内存缓冲区到文件系统缓存的过程称为刷新。需要注意的是,数据写入内存缓冲区后,并不会立即取回,而是刷新后写入文件系统caceh,即写入段后,才能获取文档此时被收回。因为内存缓冲区和文件系统缓存还没有写入磁盘,所以有丢失的可能。ES通过translog机制保证数据的可靠性。收到请求后,也会同时写入translog。当文件系统缓存中的数据写入磁盘时,translog中的数据将被清除。这个过程称为冲洗。定期触发刷新(默认为30分钟)或当translog变得太大时(默认为512MS)。为了减少磁盘IO,保证读写性能,ES中的translogES一般每隔一段时间(比如5分钟)将segment写入磁盘进行持久化。对于没有刷盘的数据,如果宕机或者掉电,那么内存中的数据就会丢失,ES是如何保证数据的可靠性的呢?这里我们说说translog。在每个shard中,写入过程分为两部分,先写入lucene,再写入translog。写完lucene文件,创建索引,此时索引还在内存中,再写translog。写入translog后,默认5S(可配置)将translog数据fsync到磁盘,请求返回给用户。这里有几个关键点:第一,它不同于数据库。数据库先写commitlog,再写内存,而ES是先写内存,再写translog。一种可能的原因是lucene内存写入逻辑非常复杂,很容易Failure,比如分词,字段长度超标等,为了避免translog中出现大量无效记录,写入内存放在前方。二是写入内存后,不可搜索。需要通过refresh将内存对象转换成一个完整的段,重新打开后才能搜索到。一般这个时间设置为1秒,这也是ES被称为NRT(nearrealtime)的原因。三、ES作为nosql数据库时,查询方式为getDocById。query可以直接从translog中查询(translog是以key/value的形式写的,key是_id,value是Doc内容),然后就变成了一个RT实时系统。第四,经过很长一段时间,比如30分钟,lucene会将内存中产生的segment刷新到磁盘中。刷新后的索引文件已经持久化,历史translog会被清空。数据(包括translog和segments)全部刷到磁盘,而fsync只是刷新translog的磁盘,也就是说es在系统掉电时最多会丢失5秒的数据。并发ES下的更新过程使用版本号这种乐观锁机制来处理并发修改问题。ES确保旧版本的数据永远不会被新版本的数据重写或覆盖。如果因为版本号冲突导致修改失败,可以使用retry_on_conflict参数设置重试次数。流程如下:收到更新请求后,从segment或translog中读取相同id的Doc,获取当前版本号。将步骤1中版本号对应的全Doc和请求中的一些字段合并成一个完整的Doc,同时更新内存中的versionMap。这时候更新请求就相当于一个索引请求。锁。再次从versionMap中读取id的最大版本号,如果versionMap没有,则从segment或translog中读取。检查版本号是否冲突(检查第1步和第4步中的版本号是否相同,相同表示不冲突,相同表示冲??突),如果有冲突,则回到initialupdatedoc阶段,再次执行;如果没有冲突,则执行最新的添加请求。在indexdoc阶段,先添加version+1,然后将doc添加到lucene中,lucene会先删除同id下已有的docid,然后添加新的doc,写入lucene成功后,更新后的版本号更新到versionMap。锁被释放,部分更新过程结束。ConcurrentreadES是通过partitioning来分布的。写入数据时,按照路由规则将数据写入到某个shard,这样海量数据就可以分布在多个shard、多台机器上,从而达到分布式的目的。因此在查询时,需要将查询请求分发到不同的shard,每个shard将查询结果汇总到client节点,client节点通过优先级队列进行二次排序,最终确定最终结果返回给用户。在初始查询阶段,查询被广播到索引中的每个分片,每个分片在本地执行搜索并构建一个大小为from+size的匹配文档的优先级队列每个分片返回其优先级队列中所有文档的ID和排序值被赋予协调节点。协调节点将这些值合并到自己的优先级队列中,生成一个全局排序的结果列表。接下来是检索阶段。协调节点识别需要检索哪些文档并将它们发送到差分分片提交多个GET请求,每个分片加载并丰富文档并将文档返回给协调器节点,一旦所有文档都被检索到,协调器节点将结果返回给客户端。三阶段查询大多数搜索系统通常使用两阶段查询。第一阶段找到匹配的DocId,第二阶段查询该DocId对应的完整文档。这个在ES中叫做query_then_fetch,另外一个是one-stagequery。当它返回完整的Doc时,这种ES称为query_and_fetch。一般情况下,第二种适用于只需要查询一个shared的请求。下图是一个两阶段查询的示意图。除了上面提到的两种,还有一种三阶段查询的情况。搜索中有一个计算逻辑是根据TF(termfrequency)和DF(documentfrequency)来计算基础分数的,但是在ES中查询的时候,是在每个shared中独立查询,而每个中的TF和DF共享也是独立的。routing虽然保证写的时候Doc是均匀分布的,但是并不能保证TF和DF是均匀的,会存在局部的TF和DF差异。出现准确的情况。这个时候基于TF,DF的分数是不准确的。为了处理这个问题,ES引入了DFS查询。比如DFS_query_the_fetch会先收集shared中所有的TF和DF值,然后将这些requests带入request,再次执行query_then_fetch,这样TF和DF在计算分数的时候就准确了。特性总结可靠性:由于Lucene的设计没有考虑可靠性,ES中使用replica和translog两种机制来保证数据的可靠性和一致性:lucence中的flushlock只保证delete和delete之间不会flushadd在update接口,但是还是有可能add完成后马上flush,导致segment是可读的,这样就不能保证primary和其他replica能同时flush,然后查询不稳定。这里只能实现最终一致性。原子性:add和delete都是直接调用lucene的接口,是原子的。部分更新时,使用版本和锁来确保更新是原子的。隔离性:仍然使用版本锁和本地锁来保持特定版本的更新数据。实时:采用周期性的刷新段到内存,重新打开段的方式,保证在短时间内(比如1秒)可以搜索到。通过将未刷新到磁盘的数据记录到translog中,保证未提交的数据可以通过ID实时访问。同时参考ES的详解-Principle:详解ES原理中读取文档的过程