1.概述
lucene作为一个开源的搜索框架,为我们提供了一个非常好的了解全文检索的窗口。在生产中,多使用基于lucene的solr和ElasticSearch两个分布式搜索框架,原因必然与其不支持分布式和官方并未提供相关的使用文档相关,所以我们可以认将lucene 视为一个搜索框架的内核。本人在此之前,在前辈指导下尝试用lucene实现过几个简单的商品搜索接口,出于好奇尝试阅读源码,现在lucene已经到8.0以上版本,网络上对于lucene源码的分析并不多见,而较为经典的资料如lucene in action等多停滞在3.0版本。图个时髦,尝试阅读8.0版本源码,就从文件锁开始,能力有限,错误的地方请指正。
2.FSLockFactory文件系统锁工厂
其继承关系如下:
其父类LockFactory是一个抽象类,内部仅定义了一个obtainLock方法,该方法在抽象类.FSLockFactory中实现,其内部仅调用了定义的新的抽象方法obtainFSLock,该方法的实现在其两个子类NativeFSLockFactory和SimpleFSLockFactory中实现。
FSLockFactory还提供了返回默认锁工厂的方法getDefault,返回的是NativeFSLockFactory单例。
3.NativeFSLockFactory(本地文件系统锁工厂)
3.1属性和内部类
属性:
/**
* Singleton instance
* 恶汉单例
*/
public static final NativeFSLockFactory INSTANCE = new NativeFSLockFactory();
//.lock文件集合(索引文件夹不只一个)
private static final Set<String> LOCK_HELD = Collections.synchronizedSet(new HashSet<String>());
NativeFSLockFactory是单例模式。
内部类:
static final class NativeFSLock extends Lock {
//文件锁,nio获得
final FileLock lock;
//文件通道
final FileChannel channel;
//被锁的文件路径
final Path path;
//文件创建时间
final FileTime creationTime;
//锁是否关闭
volatile boolean closed;
NativeFSLock(FileLock lock, FileChannel channel, Path path, FileTime creationTime) {
this.lock = lock;
this.channel = channel;
this.path = path;
this.creationTime = creationTime;
}
...
}
内部类NativeFSLock继承自抽象类Lock,并对Lock中定义的两个抽象方法ensureValid和close进行实现,可以认为,其封装了nio部分的文件锁。
3.2核心方法
protected Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException {
//待锁文件夹
Path lockDir = dir.getDirectory();
//保证存在并且是文件夹(如果是符号链接会失败)
Files.createDirectories(lockDir);
//待锁完整的路径,文件夹+文件名
Path lockFile = lockDir.resolve(lockName);
IOException creationException = null;//异常
try {
//保证文件存在
Files.createFile(lockFile);
} catch (IOException ignore) {//文件已经存在
//仅保证文件存在,至于是不是已经被创建我们不关心。
creationException = ignore;
}
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
//nio读取文件创建时间
final FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();
//将.lock文件加入set集合
if (LOCK_HELD.add(realPath.toString())) {
FileChannel channel = null;
FileLock lock = null;
try {
//以写方式打开lock文件通道,没有就创建
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) { //获取锁失败则清空,不影响下次重新加锁
IOUtils.closeWhileHandlingException(channel); // TODO: addSuppressed
clearLockHeld(realPath); // clear LOCK_HELD last
}
}
} else {//已经获得
throw new LockObtainFailedException("Lock held by this virtual machine: " + realPath);
}
}
在Debug过程中,发现在创建IndexWriter对象时该方法会被调用,传入的是我们指定的索引文件夹和一个write.lock文件。在这个方法的效果就是对索引文件夹下的.lock文件加锁,加的是进程级别的锁(进程不共享,单一进程不可重复获得,释放后可以)。
下附一张StandardOpenOption枚举表:
枚举值 | 含义 |
---|---|
READ | 以读取方式打开文件 |
WRITE | 以写入方式打开文件 |
CREATE | 如果文件不存在,创建 |
CREATE_NEW | 如果文件不存在,创建;若存在,异常 |
APPEND | 在文件的尾部追加 |
DELETE_ON_CLOSE | 当流关闭的时候删除文件 |
TRUNCATE_EXISTING | 把文件设置为0字节 |
SPARSE | 文件不够时创建新的文件 |
SYNC | 同步文件的内容和元数据信息随着底层存储设备 |
DSYNC | 同步文件的内容随着底层存储设备 |
4.思考
为什么是对索引文件夹下的.lock文件加锁,不是对整个文件夹加锁?