今天被人问到了ES相关的问题,没有说上来。ES有段时间没用了,很多细节都忘了,幸好之前有看过一些源码,这里就整理下,写篇博客记录一下。首先大致说下写索引的大概流程:
primary分片是写索引的主入口,由它负责验证输入正确性,并负责将操作复制到其他副本。首先是根据document id, route到primary shard上执行。复制的时候不需要复制完全部的replica,主节点会维护一个维护一个需要复制的列表,复制到这些shard上(parallel),然后回复client。
再单个节点上写数据的时候,会同时写索引数据和translog文件(类似HBase的WAL日志),达到一定的阈值再刷写到磁盘上。
写入流程主要分成两部分:
- 客户端包装输入数据,发送给服务端
- 服务端在各个shard上处理数据
客户端处理:
首先创建一个bulk请求:
接着定义了一个回调对象,用于存放返回结果,调用bulk.execute()方法,将请求发往服务端
真正执行的是里面的client.execute(action, beforeExecute(request), listener),nodesService 代表的是这个客户端所连接的集群的信息
通过nodes.get(index % nodes.size()) 来负载均衡,随机选取一个node,这里的index是个AtomicInteger,每次自增1来随机选取node
然后回调到了上面,这样就获取到了要把请求发往那个node…真啰嗦
到此 客户端的发送就结束了。总结下就是客户端封装输入的数据,随机选取一个节点,然后发送请求。
服务端处理:
接下来就看下服务端这边是如何的进行的。服务端的入口是这个channelHandler,就是netty处理请求的地方
服务端会检查索引是否存在,不存在的话自动建索引:
接着来到了获取shardId的地方:
内部的详细流程为:
- indexMetaData(clusterState, index) 获取到插入的这个索引的详细信息
- id = x 这个就是你输入的ID
- routing 这个没有设置过,那就是为null
即随机获取到一个shardId ,将文档索引到这个shard上
然后开始执行写主分片和备分片,执行这两部调用的还是TransportAction类中的execute():
接着到了这TransportReplicationAction.java,根据shardId和该node上记录的ShardRouting信息找到这个分片所对应的主节点,然后发往主节点上执行
然后跳到了这里,因为我这个是单机,所以直接在本地执行的,否则执行下面那句,发到远程主分片所在的节点上执行
接着就真正的在primary上开始执行了, sendLocalRequest(xxx)
跳进去看下,是对接受到的信息,创建一个异步的Primary Action来执行, run()
执行的时候要加锁:indexShard.acquirePrimaryOperationLock(onAcquired, executor);
如果检测到有副本的话,在执行主分片之前,会先把副本上要执行的操作创建好,然后再execute()
Primary上执行:
先是做了一些写之前的准备工作,主要看下有没有动态字段(有的话需要更新mapping),最后再真正执行Index:
对索引的操作都是封装在Engine对象中执行的:
接着执行indexResult = indexIntoLucene(index, plan),就是将封装好的这个Index对象写成Lucene文件,同时追加到Translog里面:
写成Lucene数据并不是立即刷新的,先写Lucene个人看到的一种说法是建索引的时候比较复杂,容易出错,避免写大量无用的记录到translog里面。
看下这个写Lucene的方法:
先要判断文档是否存在,存在的话就是更新,否则就是插入(VersionMap里面的检查逻辑暂时还没有看明白…)
执行完毕之后,会返回一个location ,代表了translog的位置
结合这个返回的location,同时也构造了另外一个和refresh有关的请求,并执行
从代码中看到,默认情况下在 maybeFlush()方法中会检查translog的大小,translog太大(超过512m), 会刷新translog文件到磁盘
每隔一秒钟(这也是ES称为近实时的原因)会被写入新的segment file,同时进入os cache,此时index segment file被打开并供search使用。然后buffer被清空,重复上述操作。
当translog大小或者时间间隔达到一定程度的时候,commit操作发生,将数据真正flush到磁盘上(包括当前buffer中的数据),同时也会并写入os cache,打开供使用。现有的translog也会被清空,创建一个新的translog。
接着回来写replica,和写primary的流程是一样的,因为掉的是同样的方法。因为ES天生是个分布式的系统,所以相当于是每次执行操作,都要发送一次请求:
至此,整个流程就执行结束了!
整个流程如下图所示:
总结下就是先写主分片,再写副本。写主分片和副本的流程是一样的。
写的时候,索引文件和日志文件是同时写的,索引文件(segment)默认每个1s生成一个,从内存缓存中写到os cache中,然后重新open并更新commit point使得文件可以被搜索。然后当日志文件达到一定大小或者时间时,讲segment文件和日志文件都刷写到磁盘上进行持久化!
两种异常情况处理:
1.主节点失效
该主节点存在的node会向master发送信息,默认等待一分钟后,master会将其他节点提升为主节点,然后请求会转发到新的主节点. 注意,这个时候可能是由于网络或者GC的问题导致主节点失联了,此时主节点还不知道它已经不是主节点了,这个时候它仍然会将请求转到打其他的replica,当它收到来自其他replica reject时,才会意识到知己已经降级了,这些操作随后会route到new primary.
2.备节点失效
主节点会向master报个这个node失联了,然后master会将这个node从in-sync replica set中移除,然后master会在其他node上重新启动一个shard。
参考:
(ES写入图解)
https://www.jianshu.com/p/7fae8e9fa7d1(ES写入简要流程描述)