热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

OSChina的全文搜索设计说明——索引过程

http:www.oschina.netquestion12_71591言:OSChina的搜索做得并不好,很久之前一直想在细节方面进行改造࿰

http://www.oschina.net/question/12_71591

言: OSChina 的搜索做得并不好,很久之前一直想在细节方面进行改造,一直也没什么好的思路。但作为整体的结构或许对大家还是有一些参考的价值,之前也分享过一些代码,这次主要是把整个模块的设计思路详细的介绍一下,本文要求了解 Lucene 的基本使用。

OSChina 使用的是全文搜索的技术,涉及到的开源软件包括 Lucene 和国产的 IKAnalyzer。谈到分词,有些人喜欢问,你怎么不用xxx呢?很不好意思,鄙人只用过和熟悉 IKAnalyzer ,所以选型的时候肯定考虑的是它。另外 IKAnalyzer 的作者就在 OSC 上,有什么问题也方便直接请教,请看之前 OSC 对 IKAnalyzer 的作者林良益的访谈。

以下内容来自百度百科:

全文搜索引擎是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

全文检索系统是按照全文检索理论建立起来的用于提供全文检索服务的软件系统。一般来说,全文检索需要具备建立索引和提供查询的基本功能,此外现代的全文检索系统还需要具有方便的用户接口、面向WWW的开发接口、二次应用开发接口等等。功能上,全文检索系统核心具有建立索引、处理查询返回结果集、增加索引、优化索引结构等等功能,外围则由各种不同应用具有的功能组成。

结构上,全文检索系统核心具有索引引擎、查询引擎、文本分析引擎、对外接口等等,加上各种外围应用系统等等共同构成了全文检索系统。

全文搜索技术的特点是:速度超快,但不及时,主要体现在刚发布的文章并不能马上搜到(这句话并不绝对,请勿纠结)。

一般全文搜索引擎都有自己独立的索引库,这个索引库跟数据库是完全隔离的,没有任何关系。Lucene 使用的是文件系统来存储索引库。当我们发布一篇文章时,这篇文章是存在于数据库中,需要通过 Lucene 提供的 API 将文章进行索引(Indexing),然后写到索引库中才能检索得到。

由于Web应用是一个多用户并发的系统,简单的说,同一个时间点可能有不止一个人在发帖,而 Lucene 的索引库支持并发读,但如果同时多人写入或者更新就会导致索引库被锁住(索引库目录下有名为 lock 文件),因此必须避免在发帖的时候同时更新索引库,一般的做法由一个独立的进程来负责索引的添加、删除和修改操作,这也就是我前面说的 “不及时” 的原因。

总结一下,如果要用 Lucene 来做全文搜索,必须注意的问题是:避免有多个线程、进程同时操作索引库,包括添加、修改和删除

下图是一个 OSChina 全文搜索的基本结构:

由于 OSC 需要做全文搜索的内容有好几种,包括:软件、新闻、问答、代码、博客,目前这几种类型的文章都是使用独立的索引库存储(这也是我想改造的一个不足之一),为了统一索引过程,我定义了一个接口 —— SearchEnabled

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package my.search;
import java.util.*;
/**
 * 支持搜索功能的Bean类需要实现该接口
 * @author  Winter Lau
 */
public interface SearchEnabled {
    /**
     * 获取搜索对象的关键字
     * @return
     */
    public long getId();
    /**
     * 返回搜索对象需要存储的字段名,例如createTime, author等
     * @return
     */
    public String[] GetStoreFields();
    /**
     * 返回搜索对象的索引字段,例如title,content
     * @return
     */
    public String[] GetIndexFields();
     
    /**
     * 返回对象的扩展信息
     * @return
     */
    public HashMap GetExtendValues();
    /**
     * 返回对象的扩展索引信息
     * @return
     */
    public HashMap GetExtendIndexValues();
     
    /**
     * 列出id值大于指定值得所有对象
     * @param id
     * @param count
     * @return
     */
    public Listextends SearchEnabled> ListAfter(long id, int count) ;
     
    /**
     * 返回文档的权重
     * @return
     */
    public float GetBoost();
}

而软件、帖子、新闻等对象对应的类必须实现 SearchEnabled 接口才能支持全文搜索。这个接口中的两个扩展方法 GetExtendValues() 和 GetExtendIndexValues() 是为了让 Bean 类能有更灵活的方式来提供索引数据,例如帖子里是包含了标签列表,这个标签本身并不是 Bean 类的一个属性,就可以通过这个方式来提供给索引器。而为什么这里有些方法是大写字母开头呢,这不符合 Java 的编码规范,这里主要是为了避免索引器把这个方法当成是 Java Bean 的 getter/setter 方法。

前面我们讲到了对索引的修改操作要独立到一个进程或者线程来处理,并保证是单实例的。OSChina 是通过一个单独的 Apache Ant 任务来执行这个索引过程,Ant 任务定义如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<target name&#61;"lucene_clean">
<delete dir&#61;"${lucene.dir}"/>
target>
<target depends&#61;"init" name&#61;"lucene_init">
<mkdir dir&#61;"${lucene.dir}"/>
target>
<target depends&#61;"lucene_init" name&#61;"lucene_build">
<echo message&#61;"Build lucene index of ${ant.project.name}"/>      
    <java classname&#61;"net.oschina.search.LuceneUpdater" classpathref&#61;"oschina.classpath" fork&#61;"true">
        <jvmarg value&#61;"-Xmx1024m" />
    java>
target>
<target depends&#61;"lucene_init" name&#61;"lucene_build_all">
<echo message&#61;"Rebuild lucene index of ${ant.project.name}"/>    
    <java classname&#61;"net.oschina.search.RebuildLuceneIndex" classpathref&#61;"oschina.classpath" fork&#61;"true">
        <jvmarg value&#61;"-Xmx1024m" />         
        <arg value&#61;"net.oschina.beans.Project" />
        <arg value&#61;"net.oschina.beans.News" />
        <arg value&#61;"net.oschina.beans.Blog" />
        <arg value&#61;"net.oschina.code.Code" />        
        <arg value&#61;"net.oschina.qa.Question" />
    java>
target>
<target depends&#61;"lucene_clean,lucene_build_all" name&#61;"lucene_rebuild"/>

然后通过 Linux 下的 crontab 每隔五分钟运行一次 lucene_build 任务&#xff0c;这个任务对应的 Java 类是 net.oschina.search.LuceneUpdater 。这个类是负责增量的构建和更新索引&#xff0c;我们会在下面详细介绍。而另外一个任务是 lucene_init &#xff0c;对应的类是 net.oschina.search.RebuildLuceneIndex&#xff0c;这相当于是对索引库进行初始化。它会读取传递进来的参数&#xff08;也就是一些Bean的类名&#xff09;&#xff0c;然后实例化这个类&#xff0c;并调用 ListAfter 方法获取记录并构建索引。

这个 RebuildLuceneIndex 主要是用于手工执行的&#xff0c;有时候需要完全重构整个索引库&#xff0c;有时候需要从某个 id 开始构建索引等等&#xff0c;这是维护上的需要。

如何实现增量的索引&#xff1f;

索引的更改包括&#xff1a;添加、修改和删除&#xff0c;Lucene 没有修改的操作&#xff0c;修改等同于删除后重建。为了实现增量操作&#xff0c;OSChina 把所有的这些操作记录到一个专门的索引任务表中&#xff0c;表名 osc_lucene_tasks  结构如下&#xff1a;

请大家不要再纠结什么 datetime 和 timestamp 的问题了&#xff0c;这不重要 :)

字段说明&#xff1a;

id  -> 记录的唯一标识
obj_id -> 对象的编号&#xff0c;例如等同于软件的编号
obj_type -> 对象类型&#xff0c;例如软件的类型是1
opt -> 操作类型&#xff1a;添加、删除或者是修改
create_time -> 操作时间
status -> 处理状态
handle_time -> 处理的时间

当我发布一个帖子时&#xff0c;obj_id 值为帖子的编号&#xff1b;obj_type 值为帖子对应类型常量&#xff0c;这里是2&#xff1b;opt 值为 OPT_ADD 常量值&#xff1b;create_time 为发帖时间&#xff1b;status 值为0表示待处理&#xff1b;handle_time 值为空。

而 LuceneUpdater 这个类会由 Linux 下的 crontab 进程每 5 分钟调用一次&#xff0c;LuceneUpdater 启动后就扫描 osc_lucene_tasks 这个表中所有 status 为待处理的记录&#xff0c;然后根据 obj_id 和 obj_type 二者的值到对应的表中读取数据&#xff0c;并根据 opt 字段指定的值来决定是添加到索引库&#xff0c;还是从索引库中删除&#xff0c;又或者是更新索引库的操作。

LuceneUpdater 完整代码如下&#xff1a;

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package net.oschina.search;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import my.db.QueryHelper;
import my.search.LuceneIndexUtils;
/**
 * 定期更新索引
 * &#64;author  Winter Lau
 * &#64;date 2010-1-4 下午04:52:12
 */
public class LuceneUpdater {
    private final static Log log &#61; LogFactory.getLog(LuceneUpdater.class);
     
    /**
     * &#64;param args
     * &#64;throws Exception
     */
    public static void main(String[] args) throws Exception {
        String sql &#61; "SELECT * FROM osc_lucene_tasks WHERE status&#61;0";
        List tasks &#61; QueryHelper.query(LuceneTask.class, sql);
        for(LuceneTask task : tasks) {
            lucene(task, true);
        }
        if(tasks.size()>0)
            log.info(tasks.size()&#43; " Lucene tasks executed finished.");
        System.exit(0);
    }
     
    public static void lucene(LuceneTask task, boolean update_status) throws Exception {
        switch(task.getOpt()){
        case LuceneTask.OPT_ADD:
            LuceneIndexUtils.add(task.object());
            break;
        case LuceneTask.OPT_DELETE:
            LuceneIndexUtils.delete(task.object());
            break;
        case LuceneTask.OPT_UPDATE:
            LuceneIndexUtils.update(task.object());
        }
        if(update_status)
            task.afterBuild();
    }
}

这就完成了索引的构建和增量更新的过程&#xff0c;而检索的操作就跟你做普通的 Lucene 检索没有什么两样。

转:https://www.cnblogs.com/fx2008/p/4093013.html



推荐阅读
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
author-avatar
轻梦云裳_904
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有