Elasticsearch-ForceMerge(一)

4. 强制段合并

  代码入口:org.elasticsearch.action.admin.indices.forcemerge.TransportForceMergeAction#shardOperation
  对于待合并处理的分片,需要先校验该分片的状态

/**
 * 判断分片状态是否为STARTED,如果已被关闭或异常,则无法merge
 */
protected final void verifyActive() throws IllegalIndexShardStateException {
    IndexShardState state = this.state;
    if (state != IndexShardState.STARTED) {
        throw new IllegalIndexShardStateException(shardId, state, "operation only allowed when shard is active");
    }
}

  进入到执行入口:org.elasticsearch.index.engine.InternalEngine#forceMerge;开始获取mergePolicy和mergeScheduler,即合并的策略和调度器(ES7.5.2默认使用TieredMergePolicy策略及ConcurrentMergeScheduler执行器)。

// 获取mergePolicy,ES
ElasticsearchMergePolicy mp = (ElasticsearchMergePolicy) indexWriter.getConfig().getMergePolicy();

  从上面的代码看到,mergePolicy是从indexWriter的config中获取的,因此看下indexWriterConfig如何生成。

// ES的mergeScheduler,调度器具体的执行过程(及Lucene自带Scheduler的异同),下面具体分析代码
private final ElasticsearchConcurrentMergeScheduler mergeScheduler;
// 初始化IndexWriterConfig
private IndexWriterConfig getIndexWriterConfig() {
  // IndexWriterConfig继承自LiveIndexWriterConfig,如果没有设置,默认为TieredMergePolicy和ConcurrentMergeScheduler
  final IndexWriterConfig iwc = new IndexWriterConfig(engineConfig.getAnalyzer());
  // 此处省略与merge无关的代码
  / ... ... /
  // 设置mergeScheduler,即上面声明的ElasticsearchConcurrentMergeScheduler
  iwc.setMergeScheduler(mergeScheduler);
  // 从config中获取mergePolicy
  MergePolicy mergePolicy = config().getMergePolicy();
  // 如果开启了softDelete,则初始化一个RecoverySourcePruneMergePolicy
  if (softDeleteEnabled) {
     mergePolicy = new RecoverySourcePruneMergePolicy(SourceFieldMapper.RECOVERY_SOURCE_NAME, softDeletesPolicy::getRetentionQuery,
         new SoftDeletesRetentionMergePolicy(Lucene.SOFT_DELETES_FIELD, softDeletesPolicy::getRetentionQuery,
             new PrunePostingsMergePolicy(mergePolicy, IdFieldMapper.NAME)));
  }
  // 设置mergePolicy
  iwc.setMergePolicy(new ElasticsearchMergePolicy(mergePolicy));
  // 使用cfs复合文件(从该角度出发,如果发现segment不是cfs,则表明很可能(而不是一定)已经做过段合并)
  iwc.setUseCompoundFile(true);
  return iwc;
 }

  现在知道了mergePolicy及mergeScheduler入口,需要来分别看下,在ES侧两者的实现过程,及与Lucene默认的异同。

4.1 MergePolicy

  在setMergePolicy中,先获取一个mergePolicy,再初始化一个ElasticsearchMergePolicy;因此,先看下传入的mergePolicy,mergePolicy从engineConfig中获取,可以在org.elasticsearch.index.shard.IndexShard #newEngineConfig中看到,mergePolicy从indexSettings中获取。

// indexSettings中初始化一个MergePolicyConfig
this.mergePolicyConfig = new MergePolicyConfig(logger, this);

// 初始化一个EsTieredMergePolicy
private final EsTieredMergePolicy mergePolicy = new EsTieredMergePolicy();
// mergePolicy的配置
MergePolicyConfig(Logger logger, IndexSettings indexSettings) {
  // 次数省略获取settings值的代码
  /... .../
    maxMergeAtOnce = adjustMaxMergeAtOnceIfNeeded(maxMergeAtOnce, segmentsPerTier);
    // 如果合并的大小,超过该索引的这个比例(默认10%),则将禁用cfs
    mergePolicy.setNoCFSRatio(indexSettings.getValue(INDEX_COMPOUND_FORMAT_SETTING));
    // 当forceMergeDeletes被调用时,只删除“段内待删除数据占比大于这个值(默认10%)的段”
    mergePolicy.setForceMergeDeletesPctAllowed(forceMergeDeletesPctAllowed);
    // 小于该值的段,其大小都将被当做该值
    mergePolicy.setFloorSegmentMB(floorSegment.getMbFrac());
    // 执行一次正常合并(非force)操作,最多可包含的段的个数
    mergePolicy.setMaxMergeAtOnce(maxMergeAtOnce);
    // 在forceMerge或forceMergeDeletes时,同一时刻在合并的段的最大个数
    mergePolicy.setMaxMergeAtOnceExplicit(maxMergeAtOnceExplicit);
    // 1.限制合并段集的总量;2.如果段的大小超过该值的一半,则不参与段合并
    mergePolicy.setMaxMergedSegmentMB(maxMergedSegment.getMbFrac());
    // 层级的概念:每次可以挑选oneMerge的总段集;例如本次需要从1~10号中,选择5个段构成oneMerge,则1~10号为一个层级
    // 每一层中,包含至少这个数量的段,才允许开始合并(如果待删除的数据量,大于下一行的值,则该条件无效)
    mergePolicy.setSegmentsPerTier(segmentsPerTier);
    // 待删除的数据量,大于该值,则上一个参数无效;该值需要在20~50之间,默认33
    mergePolicy.setDeletesPctAllowed(deletesPctAllowed);
}

  下面来看EsTieredMergePolicy的具体实现过程。

// 传入的mergePolicy,继承自FilterMergePolicy
final class EsTieredMergePolicy extends FilterMergePolicy {
    // 常规merge为Lucene侧的TieredMergePolicy
    final TieredMergePolicy regularMergePolicy;
    // forcemerge为Lucene侧的TieredMergePolicy
    final TieredMergePolicy forcedMergePolicy;

    EsTieredMergePolicy() {
        // 父类构造方法,传入一个TieredMergePolicy
        super(new TieredMergePolicy());
        // 初始化regularMergePolicy,此处的in,即为上一行父类构造方法中传入的TieredMergePolicy
        regularMergePolicy = (TieredMergePolicy) in;
        // 初始化forcemergePolicy
        forcedMergePolicy = new TieredMergePolicy();
        // 设置forceMerge合并时的最大值,强制段合并不做限制
        forcedMergePolicy.setMaxMergedSegmentMB(Double.POSITIVE_INFINITY);
    }

    // 返回待合并的段信息,即oneMerge实例的list;oneMerge表示待合并的段集
    // 假设10个段,其中1~5号需要合并一次,6~10号合并一次;则1~5号的段集构成一个oneMerge,6~10号的段集构成一个oneMerge
    @Override
    public MergeSpecification findForcedMerges(SegmentInfos infos, int maxSegmentCount,
            Map<SegmentCommitInfo, Boolean> segmentsToMerge, MergeContext mergeContext) throws IOException {
        return forcedMergePolicy.findForcedMerges(infos, maxSegmentCount, segmentsToMerge, mergeContext);
    }

    // 返回待(物理)删除数据的oneMerge实例列表
    @Override
    public MergeSpecification findForcedDeletesMerges(SegmentInfos infos, MergeContext mergeContext) throws IOException {
        return forcedMergePolicy.findForcedDeletesMerges(infos, mergeContext);
    }
    // max_merged_segment参数,只对普通merge生效;forcemerge无限制。(与其他set方法不同)
    public void setMaxMergedSegmentMB(double mbFrac) {
        regularMergePolicy.setMaxMergedSegmentMB(mbFrac);
    }
    // 此处省略mergePolicy的其他get、set方法,因为其他的set,会将参数分别设置给regularMergePolicy和forcedMergePolicy
    /... .../
}

  从上面的代码看到,传入的mergePolicy只是在内部初始化TieredMergePolicy,并设置相应的参数值;接下来看下设置的mergePolicy,即ElasticsearchMergePolicy。

/**
 * 该策略主要是对待合并段的版本进行了兼容,即比对段的版本号,将老旧的版本升级到当前版本
 */
public final class ElasticsearchMergePolicy extends FilterMergePolicy {
    // 是否需要在合并的过程中,升级版本号较低的段
    private volatile boolean upgradeInProgress;
    // 是否只升级较老的段
    private volatile boolean upgradeOnlyAncientSegments;
    // 一次可以升级的最大的段数量
    private static final int MAX_CONCURRENT_UPGRADE_MERGES = 5;
    // 构造方法
    public ElasticsearchMergePolicy(MergePolicy delegate) {
        super(delegate);
    }
    // 判断是否需要升级
    private boolean shouldUpgrade(SegmentCommitInfo info) {
        // 从当前段信息中,获取版本号
        org.apache.lucene.util.Version old = info.info.getVersion();
        // 当前ES版本对应的Lucene版本号
        org.apache.lucene.util.Version cur = Version.CURRENT.luceneVersion;
        // 只能向下兼容,不能兼容未来版本
        assert old.major <= cur.major;
        // 比较大版本号
        if (cur.major > old.major) {
            return true;
        }
        // 比较小版本号(在允许小版本差异升级的情况下)
        if (upgradeOnlyAncientSegments == false && cur.minor > old.minor) {
            return true;
        }
        // 版本足够新(或不需要对小版本进行升级),不需要升级
        return false;
    }
    // 寻找oneMerge列表
    @Override
    public MergeSpecification findForcedMerges(SegmentInfos segmentInfos,
        int maxSegmentCount, Map<SegmentCommitInfo,Boolean> segmentsToMerge, MergeContext mergeContext)
        throws IOException {
        // 如果需要升级
        if (upgradeInProgress) {
            // 初始化一个MergeSpecification(一个oneMerge的列表),将需要升级的段放进去
            MergeSpecification spec = new MergeSpecification();
            // 遍历所有的段信息
            for (SegmentCommitInfo info : segmentInfos) {
                // 判断当前段是否需要升级
                if (shouldUpgrade(info)) {
                    // 将需要升级的段加到oneMerge列表中
                    spec.add(new OneMerge(Collections.singletonList(info)));
                }
                // 需要升级的段的数量达标后,将该MergeSpecification返回去处理(将通过联级调用继续执行)
                if (spec.merges.size() == MAX_CONCURRENT_UPGRADE_MERGES) {
                    return spec;
                }
            }
            // 遍历过所有的段后,如果有需要升级的段,则返回MergeSpecification去处理
            if (spec.merges.isEmpty() == false) {
                return spec;
            }
            // 已经处理结束,不需要再升级
            upgradeInProgress = false;
        }
        // 不需要升级时,直接调用lucene接口,返回待合并的oneMerge列表
        return super.findForcedMerges(segmentInfos, maxSegmentCount, segmentsToMerge, mergeContext);
    }
    // 设置参数:upgrade表示是否升级;onlyAncientSegments表示是否只对老版本(大版本)升级
    // 这两个参数不对外,由内部直接设置;forcemerge入口处,均为false
    public void setUpgradeInProgress(boolean upgrade, boolean onlyAncientSegments) {
        this.upgradeInProgress = upgrade;
        this.upgradeOnlyAncientSegments = onlyAncientSegments;
    }
}

  综上,ES侧的mergePolicy,主要就是初始化TieredMergePolicy,分为normalMerge和forceMerge,最大的合并段大小只在normalMerge作用,forceMerge不做限制,同时对历史版本的段文件做了一次升级;而Lucene侧的TieredMergePolicy,在下面merge具体的执行过程中,再深入去看。

4.2 MergeScheduler

  在上面mergePolicy,getIndexWriterConfig部分代码,看到了mergeScheduler,es侧则是使用了ElasticsearchConcurrentMergeScheduler,下面看下这部分具体是怎么实现的。

// 此处代码在InternalEngine,为方便理解,只贴了相关代码行
// 声明一个mergeScheduler
private final ElasticsearchConcurrentMergeScheduler mergeScheduler;
// 初始化mergeScheduler
mergeScheduler = scheduler = new EngineMergeScheduler(engineConfig.getShardId(), engineConfig.getIndexSettings());
// 在indexWriterConfig中设置该mergeScheduler
iwc.setMergeScheduler(mergeScheduler);

在这里插入图片描述

  从上面看到,ElasticsearchConcurrentMergeScheduler类型的mergeScheduler,通过初始化一个EngineMergeScheduler对象获得,ElasticsearchConcurrentMergeScheduler最终是继承自Lucene的ConcurrentMergeScheduler;因此,我们先来看EngineMergeScheduler是怎么实现的。

private final class EngineMergeScheduler extends ElasticsearchConcurrentMergeScheduler {
    // 原子操作,用来记录当前参与合并的线程数量
    private final AtomicInteger numMergesInFlight = new AtomicInteger(0);
    // 原子操作,用来记录当前是否已经开始节流
    private final AtomicBoolean isThrottling = new AtomicBoolean();
    // 构造方法
    EngineMergeScheduler(ShardId shardId, IndexSettings indexSettings) {
        super(shardId, indexSettings);
    }
    // 在merge前,检查当前参与合并的线程数,是否已经达到参数值,如果达到需要节流,控制线程数
    public synchronized void beforeMerge(OnGoingMerge merge) {
        // 获取 max_merge_count值
        int maxNumMerges = mergeScheduler.getMaxMergeCount();
        // 如果当前参与合并的线程数,已经大于了max_merge_count,则需要节流
        if (numMergesInFlight.incrementAndGet() > maxNumMerges) {
            // 之前没有节流,现在要开始了
            if (isThrottling.getAndSet(true) == false) {
                // 激活节流,获取一个lock
                activateThrottling();
            }}}
    // 在merge后的操作
    public synchronized void afterMerge(OnGoingMerge merge) {
        // 获取 max_merge_count值
        int maxNumMerges = mergeScheduler.getMaxMergeCount();
        // 如果当前参与合并的线程数,小于参数值
        if (numMergesInFlight.decrementAndGet() < maxNumMerges) {
            // 设为false,表示要停止节流
            if (isThrottling.getAndSet(false)) {
                // 停止节流,释放lock
                deactivateThrottling();
            }
        }
        // 如果没有需要继续合并的任务;且距离最后一次写动作的时间,已经大于indices.memory.shard_inactive_time的值(默认5分钟)
        if (indexWriter.hasPendingMerges() == false &&
                System.nanoTime() - lastWriteNanos >= engineConfig.getFlushMergesAfter().nanos()) {
            // 从线程池中获取一个线程,去执行flush操作
            engineConfig.getThreadPool().executor(ThreadPool.Names.FLUSH).execute(new AbstractRunnable() {
                @Override
                protected void doRun() {
                    if (tryRenewSyncCommit() == false) {
                        flush();
                    }
                }
            });
        } else if (merge.getTotalBytesSize() >= engineConfig.getIndexSettings().getFlushAfterMergeThresholdSize().getBytes()) {
            // 如果一次merge的大小,超过index.flush_after_merge参数值(默认512MB),则需要触发flush操作,释放内存
            // 此处为原子操作,初始为false,由其他线程调用,获取值后进行flush操作
            shouldPeriodicallyFlushAfterBigMerge.set(true);
        }
    }
    // 此处省略异常处理部分,主要是日志打印
    /... .../
}
// 线程节流操作
public void deactivateThrottling() {
    // 获取当前可用的线程数,在创建一个线程时,该值减一
    int count = throttleRequestCount.decrementAndGet();
    // 如果可用线程数为0,则需要控制线程数
    if (count == 0) {
        // 实质是切换为NoOpLock
        throttle.deactivate();
    }
}

  从上面看到,外层的mergeScheduler主要是根据max_merge_count,在合并前后对线程数做了控制,同时,在合并结束后,触发flush操作;下面看下ElasticsearchConcurrentMergeScheduler。

// 这个scheduler是对ConcurrentMergeScheduler的补充,主要是跟踪merge,来获取合并耗时、当前合并的数据量、待合并的总数据量
class ElasticsearchConcurrentMergeScheduler extends ConcurrentMergeScheduler {
    // 此处代码省略变量的声明、日志打印,及其他get方法
    /... .../
    // 构造方法
    ElasticsearchConcurrentMergeScheduler(ShardId shardId, IndexSettings indexSettings) {
        this.config = indexSettings.getMergeSchedulerConfig();
        this.shardId = shardId;
        this.indexSettings = indexSettings.getSettings();
        // 更新配置信息,下面有该方法的具体操作
        refreshConfig();
    }
    @Override
    protected void doMerge(IndexWriter writer, MergePolicy.OneMerge merge) throws IOException {
        // 获取当前oneMerge的总文档数
        int totalNumDocs = merge.totalNumDocs();
        // 获取当前oneMerge的数据量
        long totalSizeInBytes = merge.totalBytesSize();
        // 获取系统时间
        long timeNS = System.nanoTime();
        // 当前正在merge的数量加一
        currentMerges.inc();
        // 累加当前正在合并的文档数
        currentMergesNumDocs.inc(totalNumDocs);
        // 累加当前正在合并的段大小
        currentMergesSizeInBytes.inc(totalSizeInBytes);
        // 记录正在合并的oneMerge,其内部通过id和oneMerge对应关系进行记录
        OnGoingMerge onGoingMerge = new OnGoingMerge(merge);
        // 正在合并的oneMerge集合
        onGoingMerges.add(onGoingMerge);
        try {
            // 即上面EngineMergeScheduler的节流操作
            beforeMerge(onGoingMerge);
            // 调用lucene的ConcurrentMergeScheduler,进行merge操作,后面打开看
            super.doMerge(writer, merge);
        } finally {
            // 计算merge的耗时
            long tookMS = TimeValue.nsecToMSec(System.nanoTime() - timeNS);
            // 从正在merge的集合中移除
            onGoingMerges.remove(onGoingMerge);
            // 即上面EngineMergeScheduler的节流更新,及flush操作
            afterMerge(onGoingMerge);
            // 当前正在合并的merge个数减一
            currentMerges.dec();
            // 从“正在合并的文档数”中减去已完成的文档数
            currentMergesNumDocs.dec(totalNumDocs);
            // 从“正在合并的数据量”中减去已完成的数据量
            currentMergesSizeInBytes.dec(totalSizeInBytes);
            // 总合并文档数累加
            totalMergesNumDocs.inc(totalNumDocs);
            // 总合并的数据量累加
            totalMergesSizeInBytes.inc(totalSizeInBytes);
            // 总耗时累加
            totalMerges.inc(tookMS);
            // 在OneMergeProgress中,通过reason获取stop的时间(吞吐率设为0)
            long stoppedMS = TimeValue.nsecToMSec(
                merge.getMergeProgress().getPauseTimes().get(MergePolicy.OneMergeProgress.PauseReason.STOPPED)
            );
            // 在OneMergeProgress中,通过reason获取pause的时间(吞吐率过大)
            long throttledMS = TimeValue.nsecToMSec(
                merge.getMergeProgress().getPauseTimes().get(MergePolicy.OneMergeProgress.PauseReason.PAUSED)
            );
            // stop耗时累加
            totalMergeStoppedTime.inc(stoppedMS);
            // pause耗时累加
            totalMergeThrottledTime.inc(throttledMS);
        }
    }
    // 创建并返回一个merge线程
    protected MergeThread getMergeThread(IndexWriter writer, MergePolicy.OneMerge merge) throws IOException {
        // 调用父类的方法获取,实质上父类是创建了一个守护线程,run方法则是执行doMerge
        MergeThread thread = super.getMergeThread(writer, merge);
        // 设置名字
        thread.setName(EsExecutors.threadName(indexSettings, "[" + shardId.getIndexName() + "][" + shardId.id() + "]: " + thread.getName()));
        return thread;
    }

    MergeStats stats() {
        // 初始化一个mergeStats,用来记录merge的具体信息
        final MergeStats mergeStats = new MergeStats();
        // 此处就是将上述获取的时间、文档数、数据量等塞进去
        mergeStats.add(...);
        // 返回merge信息,在InternalEngine中,提供public方法获取该对象
        return mergeStats;
    }
    // 构造方法中的配置更新
    void refreshConfig() {
        // 如果参数max_merge_count和max_thread_count与默认值不同,则需要设置更新
        if (this.getMaxMergeCount() != config.getMaxMergeCount() || this.getMaxThreadCount() != config.getMaxThreadCount()) {
            this.setMaxMergesAndThreads(config.getMaxMergeCount(), config.getMaxThreadCount());
        }
        // 每个merge的写入速率,如果该值无上限,则说明IO没有节流
        boolean isEnabled = getIORateLimitMBPerSec() != Double.POSITIVE_INFINITY;
        // 如果开启了自动IO节流,但是当前的速率没有被限制,则需要开启IO节流
        if (config.isAutoThrottle() && isEnabled == false) {
            // 开启IO节流:设置开始速度为20MB/s(IO节流具体操作将在其父类的调度中看到)
            enableAutoIOThrottle();
        } else if (config.isAutoThrottle() == false && isEnabled) {
            // 如果自动IO节流没有开启,但是速率却被限制了,需要取消限制
            disableAutoIOThrottle();
        }
    }
}

  综上,可以看到在ES侧的mergeScheduler有两种节流方式,一个是线程数,一个是IO;线程数在beforeMerge,即merge前累加当前在merge的线程数,在afterMerge中减去已完成的线程数,通过lock的切换来实现线程数节流。IO则是在ElasticsearchConcurrentMergeScheduler初始化时,更新启动速率,供后续使用(判断是否积压任务,调整速率到当前的一个百分比,有积压调整为120%,最大1GB,没有积压调整为90%,最小5MB;forcemerge则无该限制)。段合并的merge操作,是在mergeThread中进行,通过调用父类的getMergeThread获取线程。同时,ES对合并的文档数、数据量、耗时进行了记录,在InternalEngine中提供了plublic方法进行获取。

4.3 doMerge

  回归主线,继续看InternalEngine#forceMerge的执行过程。forcemerge的执行过程如下,即根据入参判断,如果只删除数据,则调用forceMergeDeletes,如果maxNumSegments参数不大于0则调用maybeMerge,否则调用forcemerge;则merge结束后,flush提交本次操作,数据刷盘,释放锁。

public void forceMerge(final boolean flush, int maxNumSegments, boolean onlyExpungeDeletes,
                           final boolean upgrade, final boolean upgradeOnlyAncientSegments) {
    // 初始化一个mergePolicy,该策略在ES侧的实现上面已经看过
    ElasticsearchMergePolicy mp = (ElasticsearchMergePolicy) indexWriter.getConfig().getMergePolicy();
    // 获取一个锁
    optimizeLock.lock();
    try {
        // 验证该分片的engine是started状态
        ensureOpen();
        // 如果需要升级(我们手动触发的forcemerge,此处是false)
        if (upgrade) {
            // 设置是否升级的参数,如何获取需要升级的segment在上面章节已经看过
            mp.setUpgradeInProgress(true, upgradeOnlyAncientSegments);
        }
        // 增加一个引用计数,防止在合并过程中,被其他操作关闭
        store.incRef();
        try {
            // 如果只处理待删除的数据
            if (onlyExpungeDeletes) {
                // 调用lucene侧的forcemergeDelete接口
                indexWriter.forceMergeDeletes(true);
            } else if (maxNumSegments <= 0) {
                // 调用lucene侧的maybeMerge接口,即ES认为的normalMerge
                indexWriter.maybeMerge();
            } else {
                // 调用lucene侧的forcemerge,具体实现下面打开看
                indexWriter.forceMerge(maxNumSegments, true);
            }
            // 合并完成后,如果需要flush
            if (flush) {
                if (tryRenewSyncCommit() == false) {
                    // 实际为调用lucene的commit接口
                    flush(false, true);
                }
            }
        } finally {
            // 释放锁
            store.decRef();
        }
    } catch (Exception e) {
      // 此处省略异常处理,主要是打印日志,抛异常
      /... ../
    } finally {
        try {
            // 重置升级参数,防止在出现异常后,参数未被重置
            mp.setUpgradeInProgress(false, false);
        } finally {
            // 释放引用
            ptimizeLock.unlock();
        }
    }
}

  我们常用的forcemerge接口,并指定maxNumSegments,默认upgrade为flase,onlyExpungeDeletes为false,flush为true;因此先来看下lucene侧的IndexWriter#forceMerge方法执行过程。

// 此处的forceMerge是org.apache.lucene.index.IndexWriter提供
// ES对索引的读写,均是通过lucene侧提供的IndexWriter、IndexReader、IndexSearcher等来操作
public void forceMerge(int maxNumSegments, boolean doWait) throws IOException {
  // 验证indexWriter是否开启,ES侧通过engine控制
  ensureOpen();
  // infoStream如果开启,则会将merge过程中的信息打印出来
  if (infoStream.isEnabled("IW")) {
    infoStream.message("IW", "forceMerge: index now " + segString());
    infoStream.message("IW", "now flush at forceMerge");
  }
  // 执行flush,对应es中的refresh操作;并且在此处会有条件的触发maybeMerge,因为下面也会再调maybeMerge,此处先跳过
  // 两个参数,分别是“是否触发merge”和“是否处理删除的数据”,后者则是在flush操作中使用
  flush(true, true);
  synchronized(this) {
    // 重置异常:实际是使用list保存merge异常的oneMerge,此处即为初始化一个空的arrayList
    resetMergeExceptions();
    // 清空merge信息:实际是使用map记录segmentInfo的状态,value则是一个布尔值
    segmentsToMerge.clear();
    // 遍历所有的segmentInfo,value给为true则表示等待被合并
    // 此处的segmentInfo是在indexWriter初始化时
    for(SegmentCommitInfo info : segmentInfos) {
      segmentsToMerge.put(info, Boolean.TRUE);
    }
    // mergeMaxNumSegments是一个全局变量,供IndexWriter使用
    mergeMaxNumSegments = maxNumSegments;
    // 遍历所有等待合并的oneMerge(在maybeMerge中会registerMerge,pendingMerges从此处赋值)
    for(final MergePolicy.OneMerge merge  : pendingMerges) {
      // 将用户指定的最多段个数,设置进每个oneMerge中
      merge.maxNumSegments = maxNumSegments;
      if (merge.info != null) {
        // 待合并的oneMerge
        segmentsToMerge.put(merge.info, Boolean.TRUE);
      }
    }
    // 遍历所有正在合并的oneMerge
    for (final MergePolicy.OneMerge merge: runningMerges) {
      // 将用户指定的最多段个数,设置进每个oneMerge中
      merge.maxNumSegments = maxNumSegments;
      if (merge.info != null) {
        segmentsToMerge.put(merge.info, Boolean.TRUE);
      }
    }
  }
  // 调用maybeMerge,触发段合并操作;对于normal merge触发的maybeMerge,maxNumSegments无上限
  maybeMerge(config.getMergePolicy(), MergeTrigger.EXPLICIT, maxNumSegments);
  // 如果指定了wait,即任务阻塞,并等待所有的merge任务结束;es的forcemerge该参数为true,不对外开放
  if (doWait) {
    synchronized(this) {
      while(true) {
        // tragedy是一个Throwable的原子引用,当不为空时,说明有异常,需要关闭indexWriter,不能再进行merge
        if (tragedy.get() != null) {
          throw new IllegalStateException("this writer hit an unrecoverable error; cannot complete forceMerge", tragedy.get());
        }
        // 如果存在merge异常
        if (mergeExceptions.size() > 0) {
          // 获取每一个合并时的异常
          final int size = mergeExceptions.size();
          for(int i=0;i<size;i++) {
            final MergePolicy.OneMerge merge = mergeExceptions.get(i);
            // 如果不是normalMerge,需要把每个线程里的异常,全部抛给主线程,一起处理
            if (merge.maxNumSegments != UNBOUNDED_MAX_MERGE_SEGMENTS) {
              throw new IOException("background merge hit exception: " + merge.segString(), merge.getException());
            }
          }
        }
        // 如果pendingMerge或者runningMerge中,有任何一个oneMerge,都指定了maxNumSegments参数,表明该oneMerge正在或等待merge(即不为默认的-1)
        if (maxNumSegmentsMergesPending())
          // 等待1秒
          doWait();
        else
          break;
      }
    }
    // 再次检查indexWriter是否已经被关闭
    ensureOpen();
  }
}

  上面看到了merge过程中,主要的三部分:1. 首先是flush,将buffer中的数据全部刷到系统缓存,并执行maybeMerge,此处的MergeTrigger类型是FULL_FLUSH,即文档提交时的合并;2. 触发maybeMerge,此处的MergeTrigger类型是EXPLICIT,表示由用户手动触发;3. 等待各个merge任务结束,如果子线程有异常,则将异常抛至主线程进行处理。
  lucene侧的flush对应es侧的refresh;lucene侧的commit对应es侧的flush;因此,上面的flush可以在es refresh模块中再看,这部分主要看maybeMerge,而maybeMerge中,实际是调用mergeScheduler的merge方法进行merge调度;从上面mergeScheduler部分看到,es使用的是ConcurrentMergeScheduler,因此直接来看ConcurrentMergeScheduler的merge方法。

public synchronized void merge(IndexWriter writer, MergeTrigger trigger, boolean newMergesFound) {
  // 合并线程数赋值:如果max_thread_count是-1,表示用户未指定,则按需要给默认值,否则什么也不做
  // 设置策略:通过数据目录,判断当前使用的是旋转盘,还是固态硬盘(此处是lucene侧的策略,非es策略)
  // 如果是旋转盘,则max_thread_count=1,max_merge_count=6
  // 如果是固态硬盘,则maxThreadCount = Math.max(1, Math.min(4, coreCount/2)),maxMergeCount = maxThreadCount+5
  // 注意:此处设置是在mergeScheduler中,而es在ElasticsearchConcurrentMergeScheduler的构造方法中就调用了refreshConfig去更新这两个参数
  // 在refreshConfig中,es已经使用了固态硬盘的参数配置进行更新;如果是旋转盘,需要注意,参数值并非1和6
  // 换句话说:lucene侧是根据磁盘类别来给默认值,而es继承后,直接将固态硬盘的推荐参数设置进去了
  initDynamicDefaults(writer);
  // 如果merge是由关闭索引时触发,我们手动触发的MergeTrigger是EXPLICIT
  if (trigger == MergeTrigger.CLOSING) {
    // 关闭IO节流
    targetMBPerSec = MAX_MERGE_MB_PER_SEC;
    updateMergeThreads();
  }
  // 一直循环,直到该indexWriter中等待merge的任务全部结束
  while (true) {

    if (maybeStall(writer) == false) {
      break;
    }
    // 获取下一个oneMerge
    OneMerge merge = writer.getNextMerge();
    // 如果下一个oneMerge为空,说明该indexWriter的合并已经完成,直接退出
    if (merge == null) {
      return;
    }
    boolean success = false;
    try {
      // 获取到oneMerge之后,需要起一个merge线程
      final MergeThread newMergeThread = getMergeThread(writer, merge);
      // 添加到merge的集合中
      mergeThreads.add(newMergeThread);
      // 更新IO节流
      updateIOThrottle(newMergeThread.merge, newMergeThread.rateLimiter);
      // 合并线程执行合并操作,线程里面执行的doMerge,而上面的ElasticsearchConcurrentMergeScheduler重写了doMerge
      // 重写的doMerge,则是在merge前后对线程数做了控制,并统计merge信息,最后调用flush
      newMergeThread.start();
      // 更新IO节流的配置(即最大速率更新)
      updateMergeThreads();
      success = true;
    } finally {
      if (!success) {
        writer.mergeFinish(merge);
      }
    }
  }
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值