背景:
ES recovery、主副分片复制会有一段时间block写入?
先说结论:
1.ES在主副本分片复制时候不会block写入(version > 2.x)
2.ES在recovery主分片时候会有一段时间block写入
主副分片同步(新增副本分片)(该部分来自于:参考2)
1.在ES 2.0 版本之前,副本recovery要经历三个阶段:需要block写一段时间(这也是最容易实现一致性的流程)(此时translog还是只有一个大文件)
流程:
phase1:主分片的lucene做快照,发送到target节点。期间不阻塞写操作,在此拷贝期间新增的数据会写到主分片的translog中。
phase2:将主分片translog做快照,发送到target节点进行重放,重放期间不会阻塞写操作。
phase3:为主分片加写锁,此时会block写入,将剩余的translog发送到target节点。由于此时数据量很小,写入过程的阻塞时间很短。
从phase1开始,就要阻止lucene执行commit操作,避免translog被刷盘后清除。
后续版本去掉了phase3的这个过程,也就意味着不再需要block写入;
2.ES2.x及之后版本
translog被重构,允许多个文件,在translog允许多文件的场景下,也就意味着允许recovery保留所需的文件,同时允许engine执行flush,以及执行lucene的commit(这将创建一个新的translog文件);
引入了translog.view概念,view是一个引用,包含了包括所有当前未提交的translog文件,以及所有未来新创建的translog文件,直到view关闭;因此可以通过该view做operations的遍历操作;
不再需要block写入的阶段,通过将拷贝期间的写入操作同时转发到正在建立的副本分片(多写)来避免copy translog过程中的实时写入数据不能被副本获取的情况,从而保证主副一致;
流程:
创建一个Translog.view,用来重放operations;
phase1:将主分片的lucene做快照,发送到target。期间允许索引操作和flush操作。发送完毕后,告知target启动engine,engine启动(副本的lucene)后,即phase1即将结束(phase2开始之前),新的索引操作都会转发副分片正常执行;
phase2:将主分片的translog做快照,发送到target去重放;
完整性:
phase2对translog的快照包含了从phase1开始的新增操作。而phase1即将结束、phase2开始之前,副分片已经可以正常处理写操作,只要把phase2的translog重放(主要是为了恢复phase1中target的engine未启动之前主分片写入的数据),就可以保证副分片不丢数据;
一致性:
由于没有了阻塞写操作的第三阶段,接下来的问题就是解决phase1和phase2之间的写操作,与phase2重放操作之间的时序和冲突问题。在phase1执行完毕后,副分片已经可以正常处理写请求,副分片的新增写操作和 translog重放的写操作是并行执行的。如果translog重放慢,又把他写回老数据怎么办(translog的数据覆盖掉新写入/更新的数据)?
es现在的机制是在写操作(新增、更新、删除)中做异常处理。新增不存在冲突,只需要判断update、delete操作的版本号是否小于lucene中doc的版本号,大于才能操作;
注:Index,Delete,都继承自Operation,每个Operation都有一个版本号,这个版本号就是lucene中doc版本号;
ES在recovery
1.recovery副本分片
副本分片从node1,move到node2的流程:
1.master节点将该副本分片标记成relocating状态,此时从主分片开始拷贝lucene文件到node2(拷贝完成之后启动node2副本分片的lucene引擎),然后走主副本分片同步数据的逻辑(如上描述);
2.完成后该副本被标记为started,然后node1中的副本分片进行删除,完成此次recovery。
注:copy过程中写入是会写到3个分片上(主、副本、recovery后的副本),‘recovery后的副本’是在阶段2(阶段1的文件拷贝完成、lucene启动后)才接受写入。
recovery副本分片的场景:相当于直接增加一个副本分片,然后再下掉老的副本分片;
此过程不会block写入;
2.recovery主分片
primary分片从node0,move到node1的过程:
1.master节点下发状态,将该主分片标记relocating,更新routing_table和routing_nodes;
2.target分片会开始复制primary的lucene文件块;此时写入请求到达source节点时,会正常写入到source主分片中(不会同时写到target)。
注意:在分片的整个relocating过程中,对target节点的请求都会被转发到source节点,直到target节点应用了master下发的分片变为STARTED的集群状态。
3.lucene文件拷贝完成后进入下一阶段。该阶段主要是target分片复制translog,并重放translog。
该阶段收到的写入请求后复制给target节点(将target分片加入到 replication group)(即source分片写入、target分片也写入)。
4.tranlog复制并重放完成意味着数据已经对齐,进入下一阶段(handoff阻塞阶段)。该阶段是为了完成主分片状态的转移,该阶段对收到的写入请求放到队列(delayedOperations)中,写入流程结束,但是不会返回响应给client,待主分片状态转移完成后继续执行,最多阻塞30分钟。delayedOperations中的操作会在本阶段的阻塞过程结束后处理。
注:该阶段时间很短,只是修改一个状态,使写入进入阻塞;
5.最后一个阶段是执行失败重试的逻辑,该阶段主要是target分片向master节点发送变更状态为started请求,并等待master下发状态变更;
上一步的阻塞阶段虽然时间很短,但是依然会有写入,delayedOperations中的数据在进入该阶段的时候处理;与该阶段的写入请求是统一的处理逻辑。
处理逻辑:
该阶段的写入请求和delayedOperations中的请求都会执行失败,抛出 ShardNotInPrimaryModeException异常,并捕获该异常,等待1分钟后重试写入,再次失败则退出;
如果1分钟内收到了新的集群状态,也会重试写入,然后写入成功。
补充解释:阻塞和失败重试阶段时间都比较短,但是完成了很多事情,阻塞阶段刚开始是primary分片开始阻塞写入,并告诉target分片可以开始变更分片状态了,此时的写入全部进入队列;
target分片响应了(primary分片接受响应)之后,开始进入“失败重试阶段”;
注:primary接受handoff响应做了什么事情?取消阻塞状态,将primary是主分片的标记清除(也就是说ES不再认为该分片是主分片)
primary开始向target发送recovery结束的消息,target分片接受到消息之后,认为自己可以成为主分片了,所以开始向master节点请求将分片标记为started状态;
master节点收到请求之后立刻下发状态变更命令,target分片状态变更完成之后,整个流程结束;
在此期间的写入请求会因为source(primary)分片已经不是主分片状态了,全部报错(但是不返回客户端),等待一分钟后重试;
图片来自参考1:
参考:
1.https://blog.51cto.com/u_15162069/2883927
2.https://www.easyice.cn/archives/231#i-2