【Elasticsearch】Elasticsearch 索引恢复流程源码分析

一:索引恢复介绍

       索引恢复是ES数据恢复过程。比如当集群宕机或者异常重启后,写入磁盘的数据先到文件系统缓存中,没有来的级刷盘,如果不通过某种方式把数据找回来,则会丢失一部分数据,找回数据丢失的过程就是索引恢复过程。

     根据数据分片的性质,索引分为主副分片,那么数据恢复就要分为主分片恢复和副分片恢复。

     主分片主要从Translog中自我恢复,尚未执行flush到磁盘的分段可以从tanslog中重建

     副分片需要从主分片中拉取Lucene分段和tanslog进行恢复,但有机会调过拉取Lucene分段阶段

     恢复工作一般经历如下图几个阶段

     

二:索引恢复流程概述

      索引恢复是由 clusterChanged 触发的,然后到IndexShard类的startRecovery执行一个特定分片的恢复流程,根据不同的恢复流程执行不同的恢复过程,其逻辑代码如下:

 

 assert recoveryState.getRecoverySource().equals(shardRouting.recoverySource());
        switch (recoveryState.getRecoverySource().getType()) {
            case EMPTY_STORE:
            case EXISTING_STORE:  //主分片本地恢复
            case PEER: //副分片从远处主分片恢复
            case SNAPSHOT: //从镜像恢复
            
             break;
}       }

从上面源码可以看出,会根据recoveryState的类型选择不同的恢复模式,其EXISTING_STORE为主分片恢复,PEER为副分片恢复;下面就对主副分片不同的恢复流程分别解析

2.1 主分片恢复

     当恢复类型为EXISTING_STORE 时候,则进行了主分片恢复,其代码进入索引恢复流程,其会初始化状态,也就是INIT阶段

case EXISTING_STORE:
                //标记开始状态恢复
                markAsRecovering("from store", recoveryState); // mark the shard as recovering on the cluster state thread
                threadPool.generic().execute(() -> {
                    try {
                        //主分片恢复主要方法
                        if (recoverFromStore()) {
                            recoveryListener.onRecoveryDone(recoveryState);
                        }
                    } catch (Exception e) {
                        recoveryListener.onRecoveryFailure(recoveryState,
                            new RecoveryFailedException(recoveryState, null, e), true);
                    }
                });

接着下来会进入StoreRecovery类的internalRecoverFromStore方法,其首先更新INDEX状态,其源码关键步骤如下,其流程可

//更新状态
indexShard.prepareForIndexRecovery();
long version = -1;
SegmentInfos si = null; 
final Store store = indexShard.store();
//读取最后一次提交的分段信息,
si = store.readLastCommittedSegmentsInfo(); 
//获取其版本号
version = si.getVersion();
//更新其版本号
recoveryState.getIndex().updateVersion(version);

上面已经到了INDEX阶段,后续将进入VERIFY_INDEX阶段,此阶段主要验证当前分片是否损坏,将检查物理和逻辑损坏,这将消耗大量的CPU资源。

经过VERIFY_INDEX 阶段后,就进入索引恢复的最重要阶段TRANSLOG阶段,其阶段会遍历所有分段,根据最后一次提交信息来确定事务日志哪些数据要重放,其代码实现在InternalEngine类的recoverFromTranslogInternal 方法中,其源码实现如下,

 private void recoverFromTranslogInternal(TranslogRecoveryRunner translogRecoveryRunner, long recoverUpToSeqNo) throws IOException {
        //根据最后一次提交信息生成Tranlog的快照
        Translog.TranslogGeneration translogGeneration = translog.getGeneration();
        final int opsRecovered;
        final long translogFileGen = Long.parseLong(lastCommittedSegmentInfos.getUserData().get(Translog.TRANSLOG_GENERATION_KEY));
        try (Translog.Snapshot snapshot = translog.newSnapshotFromGen(
            new Translog.TranslogGeneration(translog.getTranslogUUID(), translogFileGen), recoverUpToSeqNo)) {
            //重放日志
            opsRecovered = translogRecoveryRunner.run(this, snapshot);
        } catch (Exception e) {
            throw new EngineException(shardId, "failed to recover from translog", e);
        }
        // flush if we recovered something or if we have references to older translogs
        // note: if opsRecovered == 0 and we have older translogs it means they are corrupted or 0 length.
        assert pendingTranslogRecovery.get() : "translogRecovery is not pending but should be";
        pendingTranslogRecovery.set(false); // we are good - now we can commit
        //刷新磁盘
        if (opsRecovered > 0) {
            logger.trace("flushing post recovery from translog. ops recovered [{}]. committed translog id [{}]. current id [{}]",
                opsRecovered, translogGeneration == null ? null :
                    translogGeneration.translogFileGeneration, translog.currentFileGeneration());
            commitIndexWriter(indexWriter, translog, null);
            refreshLastCommittedSegmentInfos();
            refresh("translog_recovery");
        }
        translog.trimUnreferencedReaders();
    }

从上面代码可知,其会根据最后提交点生成日志快照,然后对日志进行重放。

以上就是主分片主要恢复流程,当然其后续还要经过FINALIZE阶段,这个阶段会把缓冲文件刷新到系统缓冲中,然后再执行DONE阶段,刷新数据。以上就是主分片恢复过程。

2.2 副分片恢复过程

        当恢复类型为PEER状态时候,其流程就是走副本恢复流程,其核心流程就是从主分片拉取Lucene分段和translog进行恢复,并按数据传递方向,主分片节点称为Source节点,副分片节点为Target。为什么还要拉取主分片的Translog呢?因为在副分片恢复期间,主分片是容许新的写入操作的,这些新的写入操作,需要从主分片进行重放。

      副分片恢复恢复要比主分片复杂好多,其主要可以分两个阶段:

      阶段一:在主分片所在节点,获取translog保留锁,从此时开始,会保留translog不受其刷盘清空的影响,然后调用Lucene接口把shard做快照,并把shard数据复制到副节点.

      阶段二:对translog做快照,把translog快照发到副本节点进行重放

    由于阶段1 需要通过网络复制大量的数据,过程十分长,故在后来的优化中,有两个条件可以跳过阶段一的

        1:如果可以基于请求中的sequenceNumber进行恢复,则跳过phase1

        2: 如果主副两个分片有相同的syncid且doc数量相同,也跳过阶段1

       下面就对其具体流程详细讲解,当恢复类型为PEER的状态时候,将进行副本恢复,其恢复阶段如下:

         INIT阶段   本阶段是在副本节点执行的,其把恢复任务开始时设置为INIT阶段,其副本准备向主分片节点发送StartRecoveryRequest的请求,其请求中包含本次要恢复的shard相关信息,如shardId等。

       INDEX阶段,主要负责将分片的Lucene数据复制到副分片中。

      TRANSLOG阶段 主要是讲主分片的translog数据发送到副分片节点进行重放。

       FINALIZE和DONE阶段和主分片的执行过程基本一样。

2.3 副分片恢复流程中主分片节点处理过程 

     副分片把请求发送到主节点后,主分片会对此请求进行处理,其处理流程如下图

主分片节点的主要流程是在RecoverySourceHandler类recoverToTarget方法中,其关键步骤代码如下

//分片加锁 
final Closeable retentionLock = shard.acquireRetentionLock();
resources.add(retentionLock);

final long startingSeqNo;
//判断是否可以基于sequenceNumber进行恢复
inal boolean isSequenceNumberBasedRecovery = request.startingSeqNo() != SequenceNumbers.UNASSIGNED_SEQ_NO &&
                isTargetSameHistory() && shard.hasCompleteHistoryOperations("peer-recovery", request.startingSeqNo());

//若可以基于序列号进行恢复,则获取开始的序列号
if (isSequenceNumberBasedRecovery) {
     logger.trace("performing sequence numbers based recovery. starting at [{}]", request.startingSeqNo());
     //获取开始序列号
     startingSeqNo = request.startingSeqNo();
     //发送的文件设置为空
      sendFileResult = SendFileResult.EMPTY;
 }else {
 //若不能基于序列号进行恢复,则进行阶段1的逻辑
     final Engine.IndexCommitRef phase1Snapshot;
     //也会获取startSeqNo
     startingSeqNo = Long.parseLong(
                        phase1Snapshot.getIndexCommit().getUserData().get(SequenceNumbers.LOCAL_CHECKPOINT_KEY)) + 1;
     //获取主分片的镜像    
     phase1Snapshot = shard.acquireSafeIndexCommit();
     //指定阶段一的逻辑
     sendFileResult = phase1(phase1Snapshot.getIndexCommit(), () -> estimateNumOps);

}

    

  

 

 

    

      

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值