Lock的作用是防止一个Lucene索引,同一时刻被多个IndexWriter进行写操作;如果一个Lucene索引同时被多个IndexWriter进行写操作,可能造成索引损坏。在一个Lucene索引被锁住后,Lucene索引文件夹内一定有一个write.lock文件,反之,则不一定。
NativeFSLockFactory
IndexWriter 默认使用 NativeFSLockFactory;NativeFSLockFactory锁是通过 java.nio.* APIs
实现的,所以如果这个API有什么问题,那么锁也将会失败。另外,在NFS存储环境中NativeFSLockFactory,也将会锁失败,因为可以多次获取锁,也就可以有多个IndexWriter,会造成索引损坏。在NFS环境推荐使用SimpleFSLockFactory;NativeFSLockFactory的主要好处是,如果JVM出现异常退出,操作系统会删除锁,而不是锁定文件,虽然write.lock依然存在;正常退出的情况下,write.lock也不会被删除,只是Lucene会释放write.lock文件的引用。
protected Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException {
Path lockDir = dir.getDirectory();
// Ensure that lockDir exists and is a directory.
// note: this will fail if lockDir is a symlink
Files.createDirectories(lockDir);
Path lockFile = lockDir.resolve(lockName);
IOException creationException = null;
try {
Files.createFile(lockFile);
} catch (IOException ignore) {
// we must create the file to have a truly canonical path.
// if it's already created, we don't care. if it cant be created, it will fail below.
creationException = ignore;
}
// fails if the lock file does not exist
final Path realPath;
try {
realPath = lockFile.toRealPath();
} catch (IOException e) {
// if we couldn't resolve the lock file, it might be because we couldn't create it.
// so append any exception from createFile as a suppressed exception, in case its useful
if (creationException != null) {
e.addSuppressed(creationException);
}
throw e;
}
// used as a best-effort check, to see if the underlying file has changed
final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();
if (LOCK_HELD.add(realPath.toString())) {
FileChannel channel = null;
FileLock lock = null;
try {
channel = FileChannel.open(realPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
lock = channel.tryLock(); // 获取文件锁
if (lock != null) {
return new NativeFSLock(lock, channel, realPath, creationTime);
} else {
throw new LockObtainFailedException("Lock held by another program: " + realPath);
}
} finally {
if (lock == null) { // not successful - clear up and move out
IOUtils.closeWhileHandlingException(channel); // TODO: addSuppressed
clearLockHeld(realPath); // clear LOCK_HELD last
}
}
} else {
throw new LockObtainFailedException("Lock held by this virtual machine: " + realPath);
}
}
NativeFSLock
@Override
public synchronized void close() throws IOException {
if (closed) {
return;
}
// NOTE: we don't validate, as unlike SimpleFSLockFactory, we can't break others locks
// first release the lock, then the channel
try (FileChannel channel = this.channel;
FileLock lock = this.lock) {
assert lock != null;
assert channel != null;
} finally {
closed = true;
clearLockHeld(path);
}
}
SimpleFSLockFactory
使用此API进行锁定的主要缺点是,当JVM异常退出时,可能无法释放锁。在尝试创建IndexWrite时会触发LockObtainFailedException,在这种情况下,需要手动删除文件来显式清除锁文件。前提是要确保没有其他IndexWriter在写入索引,否则很容易破坏你的Lucene索引。正常退出的情况下,会删除write.lock文件。
protected Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException {
Path lockDir = dir.getDirectory();
// Ensure that lockDir exists and is a directory.
// note: this will fail if lockDir is a symlink
Files.createDirectories(lockDir);
Path lockFile = lockDir.resolve(lockName);
// create the file: this will fail if it already exists
try {
Files.createFile(lockFile);
} catch (FileAlreadyExistsException | AccessDeniedException e) {
// convert optional specific exception to our optional specific exception
throw new LockObtainFailedException("Lock held elsewhere: " + lockFile, e);
}
// used as a best-effort check, to see if the underlying file has changed
final FileTime creationTime = Files.readAttributes(lockFile, BasicFileAttributes.class).creationTime();
return new SimpleFSLock(lockFile, creationTime);
}
SimpleFSLock
public synchronized void close() throws IOException {
if (closed) {
return;
}
try {
// NOTE: unlike NativeFSLockFactory, we can potentially delete someone else's
// lock if things have gone wrong. we do best-effort check (ensureValid) to
// avoid doing this.
try {
ensureValid();
} catch (Throwable exc) {
// notify the user they may need to intervene.
throw new LockReleaseFailedException("Lock file cannot be safely removed. Manual intervention is recommended.", exc);
}
// we did a best effort check, now try to remove the file. if something goes wrong,
// we need to make it clear to the user that the directory may still remain locked.
try {
Files.delete(path);
} catch (Throwable exc) {
throw new LockReleaseFailedException("Unable to remove lock file. Manual intervention is recommended", exc);
}
} finally {
closed = true;
}
}
SingleInstanceLockFactory
是ByteBuffersDirectory(内存索引)的默认实现,一个lock只能被一个IndexWriter使用;只在内存中存在,Lucene索引不管是以那种方式关闭,都不会影响下次锁的获取。
public final class SingleInstanceLockFactory extends LockFactory {
final HashSet<String> locks = new HashSet<>();
@Override
public Lock obtainLock(Directory dir, String lockName) throws IOException {
synchronized (locks) {
if (locks.add(lockName)) {
return new SingleInstanceLock(lockName);
} else {
throw new LockObtainFailedException("lock instance already obtained: (dir=" + dir + ", lockName=" + lockName + ")");
}
}
}
private class SingleInstanceLock extends Lock {
private final String lockName;
private volatile boolean closed;
public SingleInstanceLock(String lockName) {
this.lockName = lockName;
}
@Override
public void ensureValid() throws IOException {
if (closed) {
throw new AlreadyClosedException("Lock instance already released: " + this);
}
// check we are still in the locks map (some debugger or something crazy didn't remove us)
synchronized (locks) {
if (!locks.contains(lockName)) {
throw new AlreadyClosedException("Lock instance was invalidated from map: " + this);
}
}
}
@Override
public synchronized void close() throws IOException {
if (closed) {
return;
}
try {
synchronized (locks) {
if (!locks.remove(lockName)) {
throw new AlreadyClosedException("Lock was already released: " + this);
}
}
} finally {
closed = true;
}
}
@Override
public String toString() {
return super.toString() + ": " + lockName;
}
}
}
如果更改锁实现,则需要特别小心:首先要确保没有IndexWriter在写入Lucene索引,否则很容易破坏索引。在第一次启动新配置之前,请务必执行LockFactory更改所有Lucene实例,并清除所有剩余的锁文件。不同的实现不能一起工作!