lucene(19)—lucene增量更新和NRT(near-real-time)Query近实时查询
有时候我们创建完索引之后,数据源可能有更新的内容,而我们又想像数据库那样能直接体现在查询中,这里就是我们所说的增量索引。对于这样的需求我们怎么来实现呢?lucene内部是没有提供这种增量索引的实现的;
这里我们一般可能会想到,将之前的索引全部删除,然后进行索引的重建。对于这种做法,如果数据源的条数不是特别大的情况下倒还可以,如果数据源的条数特别大的话,势必会造成查询数据耗时,同时索引的构建也是比较耗时的,几相叠加,势必可能造成查询的时候数据缺失的情况,这势必严重影响用户的体验;
比较常见的增量索引的实现是:
- 设置一个定时器,定时从数据源中读取比现有索引文件中新的内容或是数据源中带有更新标示的数据。
- 对数据转换成需要的document并进行索引
这样做较以上的那种全删除索引然后重建的好处在于:
- 数据源查询扫描的数据量小
- 相应的更新索引的条数也少,减少了大量的IndexWriter的commit和close这些耗时操作
以上解决了增量的问题,但是实时性的问题还是存在的:
- 索引的变更只有在IndexWriter的commit执行之后才可以体现出来
那么我们怎样对实时性有个提升呢,大家都知道lucene索引可以以文件索引和内存索引两种方式存在,相较于文件索引,内存索引的执行效率要高于文件索引的构建,因为文件索引是要频繁的IO操作的;结合以上的考虑,我们采用文件索引+内存索引的形式来进行lucene的增量更新;其实现机制如下:
- 定时任务扫描数据源的变更
- 对获得的数据源列表放在内存中
- 内存中的document达到数量限制的时候,以队列的方式删除内存中的索引,并将之添加到文件索引
- 查询的时候采用文件+内存索引联合查询的方式以达到NRT效果
定时任务调度器
java内置了TimerTask,此类是可以提供定时任务的,但是有一点就是TimerTask的任务是无状态的,我们还需要对任务进行并行的设置;了解到quartz任务调度框架提供了有状态的任务StatefulJob,即在本次调度任务没有执行完毕时,下次任务不会执行;
常见的我们启动一个quartz任务的方式如下:
|
|
以上我们是设置了每三秒执行一次定时任务,而任务类是XXX
任务类通用方法
这里我定义了一个XXX的父类,其定义如下:
|
|
任务类相关实现,以下方法是获取待添加索引的数据源XXXInCreasementIndex
|
|
|
|
这里,XXX代表我们要获取数据的实体类对象
consume方法主要是做两件事:
- 数据存放到内存索引
- 判断内存索引数量,超出限制的话以队列方式取出超出的数量,并将之存放到文件索引
|
|
上边我们将内存索引和队列的实现放在了RamDirectoryControl中
内存索引控制器
首先我们对内存索引的IndexWriter进行初始化,在初始化的时候需要注意先执行一次commit,否则会提示no segments的异常
|
|
定义一个获取内存索引中数据条数的方法
|
|
此方法返回结果为TopDocs,我们根据TopDocs的totalHits来获取内存索引中的数据条数,以此来鉴别内存占用,防止内存溢出。
consume方法的实现如下:
|
|
上边的逻辑为:
根据getScoreDocsByPerPageAndSortField获取当前内存中的数据条数
根据内存中数据数量A和本次获取的数据源的总数B和内存中限制的数量C进行比较
如果A+B<=C则未超出内存索引的限制,所有数据均存放到内存
反之,判断当前内存中的数据是否已经达到限制,如果已经超出,则直接处理取出内存中的内容,然后回调此方法。
如果未达到限制,先取出未达到限制的部分,然后对剩余的进行回调。
这里我们的BeanTransferUtil是根据document转换成对应的bean的方法,此处用到了反射和commons-beanutils.jar
|
|
从内存索引中读取索引的方法如下:
|
|
此处是根据id来读取内存索引中的内容,然后将它转换成document同时删除内存中的对应记录。
NRT近实时查询的实现
对于上边的索引我们要采用适当的查询方法,这里查询时候为了达到近实时的效果,需要将内存索引添加到查询的范围中,即IndexReader中。
这里的IndexSearcher的获取方法如下:
|
|
如此,我们就可以在查询的时候既从文件索引中读取,也从内存索引中检索数据了;