Solr.IndexWriter源码分析.5

2021SC@SDUSC

/**
   * 将文档的 DocValues 字段更新为给定的值。 每场更新应用于与
    * {@link Term} 到相同的值。 所有更新都以原子方式应用,并且
    * 冲在一起。 如果文档值字段数据为 <code>null</code> 现有的
    * 值从与该术语匹配的所有文档中删除。
   *
   * 
   * @param updates
   *          the updates to apply
   *
   * @return The <a href="#sequence_number">sequence number</a>
   * for this operation
   *
   * @throws CorruptIndexException
   *           if the index is corrupt
   * @throws IOException
   *           if there is a low-level IO error
   */
  public long updateDocValues(Term term, Field... updates) throws IOException {
    ensureOpen();
    DocValuesUpdate[] dvUpdates = buildDocValuesUpdate(term, updates);
    try {
      return maybeProcessEvents(docWriter.updateDocValues(dvUpdates));
    } catch (VirtualMachineError tragedy) {
      tragicEvent(tragedy, "updateDocValues");
      throw tragedy;
    }
  }

  private DocValuesUpdate[] buildDocValuesUpdate(Term term, Field[] updates) {
    DocValuesUpdate[] dvUpdates = new DocValuesUpdate[updates.length];
    for (int i = 0; i < updates.length; i++) {
      final Field f = updates[i];
      final DocValuesType dvType = f.fieldType().docValuesType();
      if (dvType == null) {
        throw new NullPointerException("DocValuesType must not be null (field: \"" + f.name() + "\")");
      }
      if (dvType == DocValuesType.NONE) {
        throw new IllegalArgumentException("can only update NUMERIC or BINARY fields! field=" + f.name());
      }
      if (globalFieldNumberMap.contains(f.name(), dvType) == false) {
     // 如果此字段不存在,我们尝试添加它。 如果它存在并且DV类型与我们不匹配
         // 获得一致的错误消息,就像您在索引操作期间尝试这样做一样。
        globalFieldNumberMap.addOrGet(f.name(), -1, IndexOptions.NONE, dvType, 0, 0, 0, f.name().equals(config.softDeletesField));
        assert globalFieldNumberMap.contains(f.name(), dvType);
      }
      if (config.getIndexSortFields().contains(f.name())) {
        throw new IllegalArgumentException("cannot update docvalues field involved in the index sort, field=" + f.name() + ", sort=" + config.getIndexSort());
      }

      switch (dvType) {
        case NUMERIC:
          Long value = (Long)f.numericValue();
          dvUpdates[i] = new NumericDocValuesUpdate(term, f.name(), value);
          break;
        case BINARY:
          dvUpdates[i] = new BinaryDocValuesUpdate(term, f.name(), f.binaryValue());
          break;
        default:
          throw new IllegalArgumentException("can only update NUMERIC or BINARY fields: field=" + f.name() + ", type=" + dvType);
      }
    }
    return dvUpdates;
  }

  // for test purpose
  final synchronized int getSegmentCount(){
    return segmentInfos.size();
  }

  // for test purpose
  final synchronized int getNumBufferedDocuments(){
    return docWriter.getNumDocs();
  }

  // for test purpose
  final synchronized int maxDoc(int i) {
    if (i >= 0 && i < segmentInfos.size()) {
      return segmentInfos.info(i).info.maxDoc();
    } else {
      return -1;
    }
  }

  // for test purpose
  final int getFlushCount() {
    return flushCount.get();
  }

  // for test purpose
  final int getFlushDeletesCount() {
    return flushDeletesCount.get();
  }

  /**
  * 返回一组不可修改的所有字段名称为可见
    * 来自此 IndexWriter,跨索引的所有段。
    * 用于在 {@link #updateDocValues(Term, Field...)} 之前知道哪些字段存在
    * 尝试过。 如果我们可以逐步淘汰这种方法
    * {@link #updateDocValues(Term, Field...)} 可以创建不存在的
    * 根据需要添加 docValues 字段,而不是抛出
    * 尝试更新不存在的 IllegalArgumentException
   * docValues fields.
   * @lucene.internal
   * @lucene.experimental
   */
  public Set<String> getFieldNames() {
    return globalFieldNumberMap.getFieldNames(); // FieldNumbers#getFieldNames() returns an unmodifiableSet
  }

  private String newSegmentName() {
 // 无法在 IndexWriter 上同步,因为这会导致
     // 死锁
    synchronized(segmentInfos) {
  // 重要的是增加 changeCount 以便
       // 段信息在关闭时写入。 否则我们
       // 可以关闭、重新打开和重新返回同一段
       // 之前返回的名称可能导致
       // 至少有 ConcurrentMergeScheduler 的问题。
      changeCount.incrementAndGet();
      segmentInfos.changed();
      return "_" + Long.toString(segmentInfos.counter++, Character.MAX_RADIX);
    }
  }

/** 如果启用,有关合并的信息将打印到此。
   */
  private final InfoStream infoStream;

  /**
 * 强制合并策略合并段直到有
   * {@code <= maxNumSegments}。实际合并为
   * 执行由 {@link MergePolicy} 决定。
   *
   * <p>这是一个代价高昂的操作,尤其是当
   * 你传递一个小的 {@code maxNumSegments};通常你
   * 应该只在索引是静态的时候调用它(不会
   * 不再更改)。</p>
   *
   * <p>请注意,这需要成比例的可用空间
   * 到您目录中索引的大小:2X 如果您是
   * 不使用复合文件格式,如果是,则使用 3X。
   * 例如,如果您的索引大小为 10 MB,那么您需要
   * 额外的 20 MB 可用空间来完成(30 MB,如果
   * 您正在使用复合文件格式)。这也受影响
   * 通过用于执行合并的 {@link Codec},
   * 并且可能导致更大的索引。还有,最好
   * 之后调用 {@link #commit()},以允许 IndexWriter
   * 释放磁盘空间。</p>
   *
   * <p>如果合并时部分而不是全部阅读器重新打开
   * 正在进行中,这将导致 {@code > 2X} 临时
   * 那些新读者将消耗的空间
   * 保持打开当时的临时段。它是
   * 合并运行时最好不要重新打开阅读器。</p>
   *
   * <p>实际临时使用量可能远小于
   * 这些数字(取决于很多因素)。</p>
   *
   * <p>一般来说,一旦完成,总大小
   * 索引将小于起始索引的大小。
   * 它可能会小一点(如果有很多
   * 待删除)或略小一些。</p>
   *
   * <p>如果遇到异常,例如
   * 由于磁盘已满,索引不会被破坏,也不会
   * 文件将会丢失。然而,它可能有
   * 被部分合并(一些片段被合并但
   * 不是全部),并且可能是其中的一个段
   * 索引将采用非复合格式,即使
   * 使用复合文件格式。这将发生在
   * 在将段转换为时发生异常
   * 复合格式。</p>
   *
   * <p>这个调用将合并那些存在于
   * 呼叫开始时的索引。如果其他线程
   * 仍在添加文档和刷新段,那些
   * 除非您,否则不会合并新创建的段
   * 再次调用 forceMerge。</p>
   *
   * @param maxNumSegments maximum number of segments left
   * in the index after merging finishes
   * 
   * @throws CorruptIndexException if the index is corrupt
   * @throws IOException if there is a low-level IO error
   * @see MergePolicy#findMerges
   *
  */
  public void forceMerge(int maxNumSegments) throws IOException {
    forceMerge(maxNumSegments, true);
  }

/** 就像 {@link #forceMerge(int)} 一样,除了你可以
    * 指定调用是否应该阻塞直到
    * 全部合并完成。 这才有意义
    * {@link MergeScheduler} 能够运行合并
    * 后台线程。
    */
  public void forceMerge(int maxNumSegments, boolean doWait) throws IOException {
    ensureOpen();

    if (maxNumSegments < 1) {
      throw new IllegalArgumentException("maxNumSegments must be >= 1; got " + maxNumSegments);
    }

    if (infoStream.isEnabled("IW")) {
      infoStream.message("IW", "forceMerge: index now " + segString());
      infoStream.message("IW", "now flush at forceMerge");
    }
    flush(true, true);
    synchronized(this) {
      resetMergeExceptions();
      segmentsToMerge.clear();
      for(SegmentCommitInfo info : segmentInfos) {
        assert info != null;
        segmentsToMerge.put(info, Boolean.TRUE);
      }
      mergeMaxNumSegments = maxNumSegments;

      // Now mark all pending & running merges for forced
      // merge:
      for(final MergePolicy.OneMerge merge  : pendingMerges) {
        merge.maxNumSegments = maxNumSegments;
        if (merge.info != null) {
          // this can be null since we register the merge under lock before we then do the actual merge and
          // set the merge.info in _mergeInit
          segmentsToMerge.put(merge.info, Boolean.TRUE);
        }
      }

      for (final MergePolicy.OneMerge merge: runningMerges) {
        merge.maxNumSegments = maxNumSegments;
        if (merge.info != null) {
      // 这可以为空,因为我们在进行实际合并之前将合并放在 runningMerges 上,并且
           // 在_mergeInit中设置merge.info
          segmentsToMerge.put(merge.info, Boolean.TRUE);
        }
      }
    }

    maybeMerge(config.getMergePolicy(), MergeTrigger.EXPLICIT, maxNumSegments);

    if (doWait) {
      synchronized(this) {
        while(true) {
          if (tragedy.get() != null) {
            throw new IllegalStateException("this writer hit an unrecoverable error; cannot complete forceMerge", tragedy.get());
          }

          if (mergeExceptions.size() > 0) {
        // 在后台合并中转发任何异常
             //线程到当前线程:
            final int size = mergeExceptions.size();
            for(int i=0;i<size;i++) {
              final MergePolicy.OneMerge merge = mergeExceptions.get(i);
              if (merge.maxNumSegments != UNBOUNDED_MAX_MERGE_SEGMENTS) {
                throw new IOException("background merge hit exception: " + merge.segString(), merge.getException());
              }
            }
          }

          if (maxNumSegmentsMergesPending()) {
            testPoint("forceMergeBeforeWait");
            doWait();
          } else {
            break;
          }
        }
      }

// 如果在我们静止时调用 close
       // 运行,抛出异常所以调用
       // 线程会知道合并没有
       // 完全的
      ensureOpen();
    }
   // 注意:在 ConcurrentMergeScheduler 的情况下,当
     // doWait 为 false,我们可以立即返回 while
     // 后台线程完成合并
  }

/** 如果在 pendingMerges 中有任何合并,则返回 true 或
    * runningMerges 是 maxNumSegments 合并。 */
  private synchronized boolean maxNumSegmentsMergesPending() {
    for (final MergePolicy.OneMerge merge : pendingMerges) {
      if (merge.maxNumSegments != UNBOUNDED_MAX_MERGE_SEGMENTS)
        return true;
    }

    for (final MergePolicy.OneMerge merge : runningMerges) {
      if (merge.maxNumSegments != UNBOUNDED_MAX_MERGE_SEGMENTS)
        return true;
    }

    return false;
  }

/** 就像 {@link #forceMergeDeletes()} 一样,除了你可以
    * 指定调用是否应该阻塞直到
    * 操作完成。 这才有意义
    * {@link MergeScheduler} 能够运行合并
    * 后台线程。 */
  public void forceMergeDeletes(boolean doWait)
    throws IOException {
    ensureOpen();

    flush(true, true);

    if (infoStream.isEnabled("IW")) {
      infoStream.message("IW", "forceMergeDeletes: index now " + segString());
    }

    final MergePolicy mergePolicy = config.getMergePolicy();
    MergePolicy.MergeSpecification spec;
    boolean newMergesFound = false;
    synchronized(this) {
      spec = mergePolicy.findForcedDeletesMerges(segmentInfos, this);
      newMergesFound = spec != null;
      if (newMergesFound) {
        final int numMerges = spec.merges.size();
        for(int i=0;i<numMerges;i++)
          registerMerge(spec.merges.get(i));
      }
    }

    mergeScheduler.merge(mergeSource, MergeTrigger.EXPLICIT);

    if (spec != null && doWait) {
      final int numMerges = spec.merges.size();
      synchronized(this) {
        boolean running = true;
        while(running) {

          if (tragedy.get() != null) {
            throw new IllegalStateException("this writer hit an unrecoverable error; cannot complete forceMergeDeletes", tragedy.get());
          }
// 检查 MergePolicy 要求我们进行的每个合并
           // 做,看看它们中是否有任何一个仍在运行和
           // 如果其中任何一个遇到了异常。
          running = false;
          for(int i=0;i<numMerges;i++) {
            final MergePolicy.OneMerge merge = spec.merges.get(i);
            if (pendingMerges.contains(merge) || runningMerges.contains(merge)) {
              running = true;
            }
            Throwable t = merge.getException();
            if (t != null) {
              throw new IOException("background merge hit exception: " + merge.segString(), t);
            }
          }

        // 如果我们的任何合并仍在运行,请等待:
          if (running)
            doWait();
        }
      }
    }

  // 注意:在 ConcurrentMergeScheduler 的情况下,当
     // doWait 为 false,我们可以立即返回 while
     // 后台线程完成合并
  }


 /**
    * 强制合并所有已删除的段
    * 文件。 要执行的实际合并是
    * 由 {@link MergePolicy} 决定。 例如,
    * 默认的 {@link TieredMergePolicy} 只会
    * 选择一个段,如果百分比
    * 删除的文档超过 10%。
    *
    * <p>这通常是一项代价高昂的操作; 很少
    * 是否有保证。</p>
    *
    * <p>看看如何
    * 您在索引中挂起的许多删除,请致电
    * {@link IndexReader#numDeletedDocs}。</p>
    *
    * <p><b>注意</b>:此方法首先刷新一个新的
    * 段(如果有索引文档),并适用
    * 所有缓冲删除。
   */
  public void forceMergeDeletes() throws IOException {
    forceMergeDeletes(true);
  }

  /**
  * 专家:询问 mergePolicy 是否有任何合并
    * 现在需要,如果需要,运行请求的合并和
    * 然后迭代(如果需要合并再次测试)直到没有
    * mergePolicy 返回更多合并。
    *
    * 显式调用 mayMerge() 通常不是
    * 必要的。 最常见的情况是合并策略
    * 参数已更改。
   * 
   * This method will call the {@link MergePolicy} with
   * {@link MergeTrigger#EXPLICIT}.
   */
  public final void maybeMerge() throws IOException {
    maybeMerge(config.getMergePolicy(), MergeTrigger.EXPLICIT, UNBOUNDED_MAX_MERGE_SEGMENTS);
  }

  private final void maybeMerge(MergePolicy mergePolicy, MergeTrigger trigger, int maxNumSegments) throws IOException {
    ensureOpen(false);
    if (updatePendingMerges(mergePolicy, trigger, maxNumSegments) != null) {
      executeMerge(trigger);
    }
  }

  final void executeMerge(MergeTrigger trigger) throws IOException {
    mergeScheduler.merge(mergeSource, trigger);
  }

  private synchronized MergePolicy.MergeSpecification updatePendingMerges(MergePolicy mergePolicy, MergeTrigger trigger, int maxNumSegments)
    throws IOException {
// 如果 infoStream 在初始化时被禁用,但随后在某些时候启用
     // 点,再次尝试在此处记录配置:
    messageState();

    assert maxNumSegments == UNBOUNDED_MAX_MERGE_SEGMENTS || maxNumSegments > 0;
    assert trigger != null;
    if (merges.areEnabled() == false) {
      return null;
    }

// 如果发生,不要开始新的合并
    if (tragedy.get() != null) {
      return null;
    }

    final MergePolicy.MergeSpecification spec;
    if (maxNumSegments != UNBOUNDED_MAX_MERGE_SEGMENTS) {
      assert trigger == MergeTrigger.EXPLICIT || trigger == MergeTrigger.MERGE_FINISHED :
      "Expected EXPLICT or MERGE_FINISHED as trigger even with maxNumSegments set but was: " + trigger.name();

      spec = mergePolicy.findForcedMerges(segmentInfos, maxNumSegments, Collections.unmodifiableMap(segmentsToMerge), this);
      if (spec != null) {
        final int numMerges = spec.merges.size();
        for(int i=0;i<numMerges;i++) {
          final MergePolicy.OneMerge merge = spec.merges.get(i);
          merge.maxNumSegments = maxNumSegments;
        }
      }
    } else {
      switch (trigger) {
        case GET_READER:
        case COMMIT:
          spec = mergePolicy.findFullFlushMerges(trigger, segmentInfos, this);
          break;
        default:
          spec = mergePolicy.findMerges(trigger, segmentInfos, this);
      }
    }
    if (spec != null) {
      final int numMerges = spec.merges.size();
      for(int i=0;i<numMerges;i++) {
        registerMerge(spec.merges.get(i));
      }
    }
    return spec;
  }

/** 专家:由 {@link MergePolicy} 使用以避免
    * 为已经合并的段选择合并。
    * 返回的集合没有被克隆,因此是
    * 只有持有 IndexWriter 的锁才能安全访问
    * (当 IndexWriter 调用
    * 合并策略)。
    *
    * <p>集合是不可修改的。 */
  public synchronized Set<SegmentCommitInfo> getMergingSegments() {
    return Collections.unmodifiableSet(mergingSegments);
  }

  /**
  *{@link MergeScheduler} 调用此方法检索下一个
    * MergePolicy 请求的合并
    *
    * @lucene.experimental
   */
  private synchronized MergePolicy.OneMerge getNextMerge() {
    if (pendingMerges.size() == 0) {
      return null;
    } else {
      // Advance the merge from pending to running
      MergePolicy.OneMerge merge = pendingMerges.removeFirst();
      runningMerges.add(merge);
      return merge;
    }
  }

 /**
    * 如果有等待安排的合并,则返回 true。
    *
    * @lucene.experimental
    */
  public synchronized boolean hasPendingMerges() {
    return pendingMerges.size() != 0;
  }
/**
    * 关闭 <code>IndexWriter</code> 而不提交
    * 自上次提交以来发生的任何更改
    * (或自它打开以来,如果尚未调用提交)。
    * 这将删除任何已创建的临时文件,
    * 之后索引的状态将与
    * 这是最后一次调用 commit() 的时间或
    * 作家首先被打开。 这也清除了以前的
    * 调用 {@link #prepareCommit}。
    * @throws IOException 如果存在低级 IO 错误
    */
  @Override
  public void rollback() throws IOException {
  // 不要在这里调用 ensureOpen:这就像 closeable 中的“close()”。
    
     // 确保只有一个线程实际执行
     // 关闭,并确保没有正在进行的提交:
    if (shouldClose(true)) {
      rollbackInternal();
    }
  }

  private void rollbackInternal() throws IOException {
   // 确保没有提交正在运行,否则例如 我们可以在另一个线程仍在 fsync'ing 时关闭:
    synchronized(commitLock) {
      rollbackInternalNoCommit();

      assert pendingNumDocs.get() == segmentInfos.totalMaxDoc()
          : "pendingNumDocs " + pendingNumDocs.get() + " != " + segmentInfos.totalMaxDoc() + " totalMaxDoc";
    }
  }

  private void rollbackInternalNoCommit() throws IOException {
    if (infoStream.isEnabled("IW")) {
      infoStream.message("IW", "rollback");
    }
    
    try {
      synchronized (this) {
        // 必须同步,否则注册合并可能会抛出异常,如果合并
         // 同时更改,abortMerges 也同步
        abortMerges(); // this disables merges forever since we are closing and can't reenable them
        assert mergingSegments.isEmpty() : "we aborted all merges but still have merging segments: " + mergingSegments;
      }
      if (infoStream.isEnabled("IW")) {
        infoStream.message("IW", "rollback: done finish merges");
      }

     // 必须预先关闭以防它增加 changeCount 以便我们可以
       // 在调用 rollbackInternal 之前将其设置为 false
      mergeScheduler.close();

      docWriter.close(); // 首先将其标记为关闭以防止后续的索引操作/刷新
      assert !Thread.holdsLock(this) : "IndexWriter lock should never be hold when aborting";
      docWriter.abort(); // don't sync on IW here
      docWriter.flushControl.waitForFlush(); // wait for all concurrently running flushes
      publishFlushedSegments(true);// 清空刷新票证队列,否则我们可能没有清理所有资源
      eventQueue.close();
      synchronized (this) {

        if (pendingCommit != null) {
          pendingCommit.rollbackCommit(directory);
          try {
            deleter.decRef(pendingCommit);
          } finally {
            pendingCommit = null;
            notifyAll();
          }
        }
        final int totalMaxDoc = segmentInfos.totalMaxDoc();
      // 保持相同的segmentInfos 实例但替换所有
         // 它的 SegmentInfo 实例,因此下面的 IFD 将删除
         // 自上次提交以来我们刷新的任何段:
        segmentInfos.rollbackSegmentInfos(rollbackSegments);
        int rollbackMaxDoc = segmentInfos.totalMaxDoc();
      // 现在我们需要将其调整回回滚的 SI 但不要将其设置为绝对值
         // 否则我们可能会隐藏内部错误f
        adjustPendingNumDocs(-(totalMaxDoc - rollbackMaxDoc));
        if (infoStream.isEnabled("IW")) {
          infoStream.message("IW", "rollback: infos=" + segString(segmentInfos));
        }

        testPoint("rollback before checkpoint");

  // 要求删除器定位未引用的文件并删除 
  // 它们……只有当我们没有遇到悲剧时,否则 
  // 这些方法会抛出 ACE:
        if (tragedy.get() == null) {
          deleter.checkpoint(segmentInfos, false);
          deleter.refresh();
          deleter.close();
        }

        lastCommitChangeCount = changeCount.get();
        // Don't bother saving any changes in our segmentInfos
        readerPool.close();
       // 必须在我们调用 deleter.refresh 的同一个同步块内设置关闭,否则并发线程可能会尝试潜入刷新,
         // 在我们离开这个同步块之后,在我们在下面设置关闭的 finally 子句中进入同步块之前:
        closed = true;

        IOUtils.close(writeLock); // release write lock
        writeLock = null;
        closed = true;
        closing = false;
        // So any "concurrently closing" threads wake up and see that the close has now completed:
        notifyAll();
      }
    } catch (Throwable throwable) {
      try {
    // 关闭时不能持有 IW 的锁
         // mergeScheduler:这会导致死锁,
         // 例如 TestIW.testThreadInterruptDeadlock
        IOUtils.closeWhileHandlingException(mergeScheduler);
        synchronized (this) {
       // 我们试图对它好一点:做最少的
           // 如果有未决提交,则不要泄漏segments_N 文件
          if (pendingCommit != null) {
            try {
              pendingCommit.rollbackCommit(directory);
              deleter.decRef(pendingCommit);
            } catch (Throwable t) {
              throwable.addSuppressed(t);
            }
            pendingCommit = null;
          }

        // 关闭所有可以关闭的对象(但重要的是 readerPool 和 writeLock 以防止泄漏)
          IOUtils.closeWhileHandlingException(readerPool, deleter, writeLock);
          writeLock = null;
          closed = true;
          closing = false;
// 所以任何“并发关闭”线程都被唤醒并看到关闭现在已经完成:
          notifyAll();
        }
      } catch (Throwable t) {
        throwable.addSuppressed(t);
      } finally {
        if (throwable instanceof VirtualMachineError) {
          try {
            tragicEvent(throwable, "rollbackInternal");
          } catch (Throwable t1){
            throwable.addSuppressed(t1);
          }
        }
      }
      throw throwable;
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值