本文档旨在分析Lucene如何把业务信息写到磁盘上的大致流程,并不涉及Document中每个Field如何存储(该部分放在另外一篇wiki中介绍)。
一,Lucene建索引API
二,创建IndexWriter
NIOFSDirectory.open()
|
如果是64位JRE会得到MMapDirectory(采用内存映射的方式写索引数据到File中)。
可以对IndexWriter做一些属性配置,IndexWriterConfig里面有非常丰富的各种配置。
三,创建Document
这个步骤比较简单,主要是将业务字段组装成一个Document。一个Document由多个Field组成的。
每个Filed一般有四个属性组成:
- name:该字段的名称
- value:该字段的值
- value是否需要存储到索引文件中:如果存储到索引文件中,则search的时候可以从Document中读取到该字段的值
- value值是否被索引:如果该字段被索引,则可以通过该字段为条件进行检索
四,添加Document
添加一个Document,其实调用的是updateDocument。而Lucene更新Document不像Mysql可以直接更新某一条记录,所以只能先删除这条记录(Document),然后再添加上这条Document。下面参数Term,是一个检索条件,满足条件的Document做更新。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
void
updateDocument(Term term, Iterable<?
extends
IndexableField> doc)
throws
IOException {
ensureOpen();
try
{
boolean
success =
false
;
try
{
if
(docWriter.updateDocument(doc, analyzer, term)) {
processEvents(
true
,
false
);
}
success =
true
;
}
finally
{
if
(!success) {
if
(infoStream.isEnabled(
"IW"
)) {
infoStream.message(
"IW"
,
"hit exception updating document"
);
}
}
}
}
catch
(AbortingException | OutOfMemoryError tragedy) {
tragicEvent(tragedy,
"updateDocument"
);
}
}
|
1 Lucene使用场景
这里从下面几个角度阐述下为什么Lucene不能直接更新一个Document?
- Lucene的设计本质是一个面向检索,或者面向读的系统。为了方面的检索,在建立索引的时候做了大量的读优化存储设计。简而言之,为了读的性能,牺牲了方便写、更新的操作。
- Lucene使用背景暗含了:Lucene适合(擅长)频繁读,不常写的场景。
所以上面添加一个Document,最后演变成了更新一个Document。并且updateDocument包含两个串行操作
(1)先检索,如果有满足条件的Document,则删除
(2)如果没有满足条件的Document,则直接添加到内存中
2 重要的几个基础类
在看docWriter.updateDocument(doc, analyzer, term)代码之前,我们先看几个Lucene子建的类,下面着重分析下:
2.1 DocumentsWriterPerThreadPool
Lucene内部实现的一个DocumentsWriterPerThread池(并不是严格意义的线程池),主要是
实现DocumentsWriterPerThread的重用(准确来说是实现ThreadState的重用)。该类可以简单理解一个线程池。
2.2 ThreadState
本质是个读写锁,用来配合DocumentsWriterPerThread来完成对一个Document的写操作。
2.3 DocumentsWriterPerThread
简单理解成一个Document的写线程。线程池保证了DocumentsWriterPerThread的重用。
2.4 DocumentsWriterFlushControl
控制DocumentsWriterPerThread完成index过程中flush操作
2.5 FlushPolicy
刷新策略
理解了ThreadState这个类应该就简单了,甚至可以直接把该类看做带读写锁控制的写线程。其实是ThreadState内部引用DocumentWriterPerThread实例。在线程池初始化的时候就创建了8个ThreadState(这个时候并没有初始化,意思是DocumentWriterPerThread并没有新建起来,而是延迟初始化具体线程)。后面就尽量重用这个8个ThreadState。
3 docWriter.updateDocument
好了,看完了几个基础类,回到上面updateDocument最关键的是这一行。
4 docWriter.updateDocument详细步骤
-
从线程池中获取一个ThreadState
- 初始化ThreadState的线程DocumentsWriterPerThread
- 该线程更新Document
- 该线程重新回到线程池中。线程池中维护了一个freeList,可重用的ThreadState都放到该freeList里面