索引相关文档
smartbi右上角的搜索栏、血统分析、影响性分析等。
如果想要搜索标题含有关键字的数据时,在数据库中搜索往往是使用 where 标题 like 关键字,此时会导致索引失效全表扫描,数据量大时会非常的慢。如果还想搜索内容也包含关键字时,需要拼接or条件,导致搜索的内容,速度也越慢。
这时候就可以使用到全文检索引擎Lucene进行优化搜索的速度。
Lucene:全文检索引擎的架构,提供了完整的查询引擎和索引引擎。
以文件的方式将索引文件存储在磁盘中,当需要使用的时候读取索引文件进行查询。
-
字段Field:数据库中的每个字段
-
文档Document:由多个Field组成,用于记录索引信息的数据结构单元,
-
目录对象Directory:索引文件的物理存储位置
-
索引写出器IndexWriter:索引写出工具,作用是实现对索引文件的增删改
-
写出器的配置对象:需要分词器和lucene的版本
创建索引前,需要
1、创建文档对象。确认那些信息是参与检索的,如标题、描述等。将相关信生成一个个的Field,添加到索引的索引数据单元Doc中。
2、配置IndexWriter。如指定索引文件位置Directory、配置好IndexWirterConfig等对象。
3、创建索引。只需要将前面创建的索引数据Doc传入到配置好的indexWriter中,生成索引文件。
Java // 创建索引public void testCreate() throws Exception{ //1 创建文档对象 Document document = new Document(); // 创建并添加字段信息。参数:字段的名称、字段的值、是否存储,这里选Store.YES代表存储到文档列表。Store.NO代表不存储 document.add(new StringField("id", "1", Field.Store.YES)); // 这里我们title字段需要用TextField,即创建索引又会被分词。 document.add(new TextField("title", "谷歌地图之父跳槽facebook", Field.Store.YES)); //2 配置IndexWriter // 索引目录类,指定索引在硬盘中的位置 Directory directory = FSDirectory.open(new File("d:\indexDir")); // 创建分词器对象 Analyzer analyzer = new StandardAnalyzer(); // 索引写出工具的配置对象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer); // 创建索引的写出工具类。参数:索引的目录和配置信息 IndexWriter indexWriter = new IndexWriter(directory, conf); //3 创建索引 // 把文档交给IndexWriter indexWriter.addDocument(document); // 提交 indexWriter.commit(); // 关闭 indexWriter.close();} |
|---|
1、通过smartbi.metadata.MetadataModule.getCatalogDocument(ICatalogElement)方法,生成到资源树中资源节点的Doc对象。
| getCatalogDocument返回的Doc对象并不是Lucene的Doc对象,而是smartbi自定义的IDocument对象,在后面addDocuments之前,会通过IndexService的getDocument(IDocument)方法将IDocument转换成Lucene的Document。 |
|---|
2、在添加、修改索引前,会调用smartbi.index.IndexService.ensureIndexWriter(boolean, MaxFieldLength)检查indexWriter是否配置好,如果没有则根据信息进行配置。
3、调用indexWriter.addDocument生成索引文件。
-
索引读取工具IndexReader:读取索引的工具,用于构建索引搜索工具IndexSearcher
-
索引搜索工具IndexSearcher:作用是实现索引的快速搜索、排序、打分等功能
-
查询条件Query:查询对象,包含要查询的关键词信息
-
匹配的文档信息TopDocs:包含查询到的总条数信息、所有符合条件的文档的编号信息。
-
得分文档对象ScoreDoc:包含文档的编号、文档的得分。
搜索索引的过程:
1、需要先构建索引搜索工具。从索引文件中获取到Directory对象,再使用索引读取工具IndexReader将Directory中的索引信息读取出来,才能使用搜索工具进行搜索。
2、构建查询对象Query,可以通过QueryParser解析关键字得到查询对象或者Query的子类直接创建查询对象,实现高级查询。
3、调用IndexSearcher.search()方法,获取到到文档信息。
| Java public void testSearch() throws Exception { // 1、构建索引搜索工具 // 索引目录对象 Directory directory = FSDirectory.open(new File("d:\indexDir")); // 索引读取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 2、构建查询对象Query // 创建查询解析器,两个参数:默认要查询的字段的名称,分词器 QueryParser parser = new QueryParser("title", new IKAnalyzer()); // 创建查询对象 Query query = parser.parse("谷歌"); // 3、search获取到到文档信息 // 搜索数据,两个参数:查询条件对象要查询的最大结果条数 // 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。 TopDocs topDocs = searcher.search(query, 10); // 获取总条数 System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据"); // 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取出文档编号 int docID = scoreDoc.doc; // 根据编号去找文档 Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); // 取出文档得分 System.out.println("得分: " + scoreDoc.score); }} |
|---|
1、在搜索前,会调用smartbi.index.IndexService.ensureIndexSearcher检查indexSearcher是否配置好,如果没有则根据信息进行配置。
2、IndexService.getSearchQuery(String, List, String)将关键字和过滤的资源类型等搜索条件进行组合,生成出Query。
3、调用indexSearcher.search后,得到的文档信息。
当需要查看索引的时候进行调试的时候,可以通过使用索引查看工具Luke查看索引文件中的Doc对象。
-
下载Luke。
-
运行luke.jnlp。
-
出现界面在path输入索引文件的路径。
-
接下来再Search中输入搜索条件即可查看对应的索引信息。
在了解索引前,首先要了解几个相关的对象及其用途。
-
org.apache.lucene.document.Document:
lucene索引的数据结构单元,用于记录索引信息。存储多个字段Field。就像数据库的记录-字段。
-
smartbi.index.IDocument
smartbi中自定义的document,可供自定义索引需要存储的信息如内容、路径、引用资源和多语言等。
-
smartbi.metadata.IReference
各模块处理引用资源的类,其中定义了抽象方法:accept、getResource分别用于判断可以处理资源和获取引用资源。
-
smartbi.metadata.assist.RefResource
引用资源的抽象,存储着资源所引用的resIds、路径、内容等信息。
在代码中对索引文件的具体操作都在IndexService中进行,而对索引的应用在MetaDataModule中进行,MetaDataModule处理完业务后会通过命令通知IndexService中的线程进行处理,使用了类似命令模式的结构。
当业务需要新增/更新索引文件的时候,则会调用IndexService.addCommand(IIndexCommand),将命令添加到队列cmdQueue。在索引处理线程中会调用CmdExecutor.run,取出cmdQueue,并调用execute方法进行命令的执行。
入口:smartbi.metadata.MetadataModule.addDocument(String)
1、调用getCatalogDocumentById获取到document对象,如果是新增的资源,则会在里面处理,根据资源的各种信息生成新的docus对象。
2、将进行更新索引命令和docs传入addDocsCmd(IndexCommandType.ADD_DOCUMENT, docs),执行。
3、进特殊处理不在资源树的资源信息。
在获取document对象时,会将该资源的依赖资源id都设置进document中的Refid中。
入口:smartbi.metadata.MetadataModule.getCatalogDocument(ICatalogElement)方法。
在该方法中会遍历注册在MetaDataModule中的Reference,使用accept()进行判断该资源的类型是否在该Reference的处理范围中。如果是则调用ref.getResource(catalog)方法,处理并获取到对应的依赖资源refIdList,添加到返回的RefResource中。
RefResource的UML图:
| 如果一个资源有不止一个Reference可以accept该资源,则第一次处理返回RefResource,第二次以后则直接用第一次返回的RefResource进行merge,将依赖资源添加进去。 |
|---|
入口:smartbi.metadata.MetadataModule.deleteDocuments(Set)
将需要删除的id传入,生成ManualDocument对象。再发送删除索引命令将ManualDocument对象传入IndexService的方法中。
搜索资源主要有几个用途:
-
搜索栏的搜索:搜索含有关键字的资源,MetadataModule.searchByKeywordsExactly()
-
影响性分析:搜索引用到资源A的资源,MetadataModule.searchByReferenced()
搜索索引中refId、content、type包含了被搜索资源Aid或者name的资源,再递归获取到引用到引用资源的资源,最后返回一个Document的列表,这个列表就是资源A所有被引用到的所有资源。
-
血统分析:搜索资源A引用到的资源,MetadataModule.searchReferringTo()
通过A的Id搜索到对应资源的doc,提取doc中的refId,获取到该资源的所有依赖资源,再递归获取到依赖资源的依赖资源,最后返回一个Document的列表,这个列表就是资源A所引用到的所有资源。
因为索引文件是存在磁盘中,资源树的节点信息则是存在知识库中。需要两边的资源信息能对应上,才能正常的使用到索引功能。当两边的索引信息不一致的时候,则需要重建索引,将知识库中的资源信息读取出来,重新生成索引文件。
要确认知识库的资源信息和索引中的资源信息是否不一致,则需要一个记录进行比对,这个记录便是索引的版本号。
知识库中的索引版本号存在t_systemconfig表的INDEX_VERSION中:
索引文件中的版本号是作为一个手工创建的索引存在,id为DOC_ID_INDEX_VERSION,name为版本号的索引文件。
当addToQueue操作索引时,如重建索引会调用到RemoteIndexNotifier.addIndexVersion(cmd)方法,如果syncRemoteIndex为true时将数据库中的索引版本和索引文件中的索引版本进行更新。
| 索引版本号只能用于确认索引不一致,无法确认是否完全一致。即使版本号相同,两边的索引也可能不一致。 |
|---|
Smartbi在启动的时候会判断索引信息,当版本不一致的时候,则会进行一次不清空索引的索引重建。
当需要重建索引的时候,可能大部分的索引都是正常的,只有部分资源的索引有问题。为了提高重建索引的效率,在EPPR-45100中修改了重建索引的逻辑,将索引分成清空索引再重建和不清空索引重建。当用户判断环境中大部分索引都有问题的时候,才需要进行先清空再进行重建。
清空索引重建过程:
-
标志开始重建索引:执行REBUILD_INDEX_BEGIN命令,用于计时、更新索引重建的状态、通知其他集群进行重建。
-
获取需要忽略重建索引的资源:忽略一些不需要创建索引的资源,如多维查询的辅助多维分析及多维参数等。
-
重建资源树的索引:先执行命令CLEAN_INDEX清空所有的索引,再获取资源树中所有除了忽略以外节点的Document,并发送IndexService进行重建。
-
重建其他资源的索引:重建除了资源树以外需要索引的资源,如宏模块、用户、操作和扩展包的资源。
-
更新索引的版本号:更新知识库中和索引文件中的版本信息。
-
标志索引重建结束:更新索引重建的状态、打印重建耗时。
由于大部分情况下,资源没被改变索引文件就不会被改动的。而每次重建索引都需要将已经重建好的索引进行删除再重建,在资源树比较多时候会十分浪费时间。所以在大部分情况下,将可能不正常的索引才进行重建,能提高重建索引的效率。
| 如何去确认哪些资源是索引可能是不正常的? |
|---|
当我们修改资源的时候,资源的lastModifiedDate会被修改成最新的时间,该资源的索引也会进行重建,索引版本号和修改时间也都会被更新。所以lastModifiedDate在索引版本号修改时间以前的资源索引大概率是正常的,而lastModifiedDate在索引版本号修改时间以后的资源,则证明该资源被修改以后索引没有被更新,这些资源大概率索引是不正常的。
不清空索引重建过程:
-
标志开始重建索引。
-
获取到索引版本的修改时间。
-
获取需要忽略重建索引的资源:除了不需要创建索引的资源外,还忽略了资源索引的LastModified和资源树LastModified相同的资源。
-
重建资源树的索引:根据a步骤中获取到的时间lastModified,获取到最后修改时间迟于lastModified的资源树节点的Docs,发送IndexService进行重建。
-
……与清空重建相同
由于创建索引需要比较长的时间,当smartbi恢复知识库或者空库启动等修改到比较多资源树节点和索引的操作时,在主线程中自动创建索引会导致用户比较长时间的等待。
为了优化用户的体验,在启动和恢复等这类可能会导致比较长时间创建索引而且又不是用户主动去进行的操作时,都会新开一个线程进行索引的创建。而在多线程的环境下,容易导致一些多线程的问题如死锁、重建后状态不对的出现。
新开线程进行重建:
调用了JobService.submit()方法,将重建索引在自定义的Callable类中进行。由于启动时候没有对应的session进行创建,所以使用自定义的fakeSession进行,需要在索引重建完成以后再恢复原来的session。