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;
}
}