再聊聊ES索引文档的流程

文档索引步骤

再聊聊ES索引文档的流程_第1张图片

  1. 客户端向node1发送新建,查询或删除请求。
  2. 节点使用文档的_id确定文档属于分片0,请求会被转发到node3,因为分片0的主分片目前被分配在node3上
  3. node3在主分片上面执行请求,如果成功了,它会将请求并行转化到node1与node2的副本分片上,一旦所有的副本分片都报告成功,node3将向协调节点报告成功,协调节点向客户端报告成功。

上面是按单个文档操作的,多个文档在使用bulk操作时和上面流程差不多,这里不再多说。

文档索引过程详解

整体流程图。
再聊聊ES索引文档的流程_第2张图片

  • 协调节点默认使用文档ID参与计算(也支持通过routing), 以便为路由提供合适的分片。

    shard = hash(document_id) % (num_of_primary_shards)
    
  • 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到memory buffer,然后定时(默认1秒)写入到filesystem cache(操作系统文件缓存,这里不由JVM管理),从memory buffer到filesystem cache的过程就叫refresh。需要注意的是数据写入memory buffer后并不以马上被检索,而只有经过refresh写入到filesystem caceh后也就是写入到segment之后,此时文档才可以被检索到。
  • 因为memory buffer与filesystem cache都还未写入磁盘所以会有丢失的可能。ES是通过translog机制来保证数据的可靠性的。在接收到请求后,同时也会写入到translog,当filesystem cache中的数据写入到磁盘后才会清除translog里的数据,这个过程称flush。flush是定时触发(默认30分钟)或translog变得太大(默认为512MS)。

并发下的update流程

ES使用版本号这种乐观锁的机制处理并发修改问题,ES保证了一个老版本的数据永远无法重写或覆盖更新版本的数据,如果因版本号冲突修改失败可以使用retry_on_conflict参数设定重试次数。流程如下:

  1. 收到update请求后,从segment或者translog中读取同id的Doc,并获取此时版本号。
  2. 将第1步版本号对应的全量Doc和请求中的部分字段合并为一个完整的Doc,同时更新内存中的versionMap。这个时候update请求相当于一个index请求了。
  3. 加锁。
  4. 再次从versionMap中读取该id最大版本号,如果versionMap没有,则从segment或translog里读。
  5. 检查版本号是否冲突(检查第1步与第4步的版本号是否相同,相同为不冲突,不相同为冲突),如果冲突则回退到开始的update doc阶段重新执行;如果没冲突则执行最新的add请求。
  6. 在index doc阶段,首先将version+1,再将doc加入到lucene中,lucene会先删除同id下已存在的doc id,然后再增加新doc,写入lucene成功后,将更新后的版本号更新到versionMap。
  7. 释放锁,部分更新流程结束。

ES里的translog

ES为了减少磁盘IO保证读写性能,一般是每隔一段时间(比如5分钟)才会将segment写入磁盘持久化,对于还未flush到磁盘的数据,如果发生宕机或掉电,那么内存里的数据是会丢失的,ES是如何保证数据的可靠性的呢?这里我们来说下translog。
再聊聊ES索引文档的流程_第3张图片
在每个shard中,写入流程分两部分,先写入lucene,再写入到translog。写完lucene文件创建好索引后,此时索引还在内存里,接着去写translog,写完translog后,刷新translog数据到磁盘上(这个是可配置的,可能会立即flush到磁盘也可能间隔一段时间再flush),写磁盘成功后,请求返回用户。这里有几个关键点:

  1. 一是和数据库不同,数据库是先写commitlog,然后再写内存,而ES是先写内存再写translog,一种可能原因是lucene内存写入有很复杂的逻辑,容易失败,比如分词,字段长度超限等,为了避免translog里有大量无效记录,就将写内存放到了前面
  2. 二是写入内存后,并不是可搜索的,需要通过refresh将内存的对象转换成完整的segment后,然后再次reopen后才能被搜索,一般这个时间设置为1秒,这也是ES被称为NRT(near real time)的原因
  3. 三是当ES作为nosql数据库时,查询方式是getDocById,这种查询可以直接从translog中查询(translog是以key/value形式写入的,key是_id,value是Doc内容),这时就成了RT实时系统了。
  4. 四是每隔一段较长时间,比如30分钟后,lucene会将内存中生成的segment刷新到磁盘上,刷新后的索引文件已经持久化了,历史的translog会被清掉

特性总结

  • 可靠性:由于lucene的设计不考虑可靠性,在ES中通过replica和translog两套机制保证数据的可靠性
  • 一致性:lucence中的flush锁只保证update接口里的delete和add中间不会flush,但是add完成后仍然有可能立即发生flush,导致segment可读,这样就没法保证primary和其他replica可以同一时间flush,进而出现查询不稳定的情况,这里只能实现最终一致性。
  • 原子性:add与delete都是直接调用lucene的接口,是原子的。当部分更新时,使用version和锁保证更新是原子的。
  • 隔离性:仍然采用version和局部锁来保住更新的是特定版本的数据
  • 实时性:使用定期refresh segment到内存,并且reopen segment方式保证搜索可以在较短时间(比如1秒)内被搜索到。通过将未刷新到磁盘数据记入translog,保证对未提交数据可以通过ID实时访问到

你可能感兴趣的:(再聊聊ES索引文档的流程)