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<String, String> GetExtendValues();
 
    /**
     * 返回对象的扩展索引信息
     * @return
     */
    public HashMap<String, String> GetExtendIndexValues();
     
    /**
     * 列出id值大于指定值得所有对象
     * @param id
     * @param count
     * @return
     */
    public List<? extends 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
<!-- Lucene Tasks -->
<target name="lucene_clean">
<delete dir="${lucene.dir}"/>
</target>
 
<target depends="init" name="lucene_init">
<mkdir dir="${lucene.dir}"/>
</target>
 
<target depends="lucene_init" name="lucene_build">
<echo message="Build lucene index of ${ant.project.name}"/>      
    <java classname="net.oschina.search.LuceneUpdater" classpathref="oschina.classpath" fork="true">
        <jvmarg value="-Xmx1024m" />
    </java>
</target>
 
<target depends="lucene_init" name="lucene_build_all">
<echo message="Rebuild lucene index of ${ant.project.name}"/>    
    <java classname="net.oschina.search.RebuildLuceneIndex" classpathref="oschina.classpath" fork="true">
        <jvmarg value="-Xmx1024m" />         
        <arg value="net.oschina.beans.Project" />
        <arg value="net.oschina.beans.News" />
        <arg value="net.oschina.beans.Blog" />
        <arg value="net.oschina.code.Code" />        
        <arg value="net.oschina.qa.Question" />
    </java>
</target>
 
<target depends="lucene_clean,lucene_build_all" name="lucene_rebuild"/>

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

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

如何实现增量的索引?

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

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

字段说明:

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

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

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

LuceneUpdater 完整代码如下:

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;
 
/**
 * 定期更新索引
 * @author  Winter Lau
 * @date 2010-1-4 下午04:52:12
 */
public class LuceneUpdater {
 
    private final static Log log = LogFactory.getLog(LuceneUpdater.class);
     
    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        String sql = "SELECT * FROM osc_lucene_tasks WHERE status=0";
        List<LuceneTask> tasks = QueryHelper.query(LuceneTask.class, sql);
        for(LuceneTask task : tasks) {
            lucene(task, true);
        }
        if(tasks.size()>0)
            log.info(tasks.size()+ " 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();
    }
 
}

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

OSChina 的全文搜索设计说明 —— 索引过程的更多相关文章

  1. 如何在Web前端实现CAD图文字全文搜索功能之技术分享

    现状 在CAD看图过程中我们经常会需要用到查找文字的功能,在AutoCAD软件查找一个文字时,可以通过打开左下角输入命令find,输入查找的文字,然后设置查找范围,就可以搜索到需要查询的文字.但在We ...

  2. OSCHina技术导向:Java全文搜索框架Lucene

    Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎.Lucene的目的是为软件开发人员提供一个简单易用 ...

  3. lucene全文搜索之四:创建索引搜索器、6种文档搜索器实现以及搜索结果分析(结合IKAnalyzer分词器的搜索器)基于lucene5.5.3

    前言: 前面几章已经很详细的讲解了如何创建索引器对索引进行增删查(没有更新操作).如何管理索引目录以及如何使用分词器,上一章讲解了如何生成索引字段和创建索引文档,并把创建的索引文档保存到索引目录,到这 ...

  4. lucene全文搜索之三:生成索引字段,创建索引文档(给索引字段加权)基于lucene5.5.3

    前言:上一章中我们已经实现了索引器的创建,但是我们没有索引文档,本章将会讲解如何生成字段.创建索引文档,给字段加权以及保存文档到索引器目录 luncene5.5.3集合jar包下载地址:http:// ...

  5. lucene全文搜索之二:创建索引器(创建IKAnalyzer分词器和索引目录管理)基于lucene5.5.3

    前言: lucene全文搜索之一中讲解了lucene开发搜索服务的基本结构,本章将会讲解如何创建索引器.管理索引目录和中文分词器的使用. 包括标准分词器,IKAnalyzer分词器以及两种索引目录的创 ...

  6. 全文搜索技术—Lucene

    1.   内容安排 实现一个文件的搜索功能,通过关键字搜索文件,凡是文件名或文件内容包括关键字的文件都需要找出来.还可以根据中文词语进程查询,并且支持多种条件查询. 本案例中的原始内容就是磁盘上的文件 ...

  7. SQLSERVER全文搜索

    SQLSERVER全文搜索 看这篇文章之前请先看一下下面我摘抄的全文搜索的MSDN资料,基本上MSDN上关于全文搜索的资料的我都copy下来了 并且非常认真地阅读和试验了一次,并且补充了一些SQL语句 ...

  8. [Elasticsearch] 全文搜索 (一) 基础概念和match查询

    全文搜索(Full Text Search) 现在我们已经讨论了搜索结构化数据的一些简单用例,是时候开始探索全文搜索了 - 如何在全文字段中搜索来找到最相关的文档. 对于全文搜索而言,最重要的两个方面 ...

  9. MySQL 全文搜索支持, mysql 5.6.4支持Innodb的全文检索和类memcache的nosql支持

    背景:搞个个人博客的全文搜索得用like啥的,现在mysql版本号已经大于5.6.4了也就支持了innodb的全文搜索了,刚查了下目前版本号都到MySQL Community Server 5.6.1 ...

随机推荐

  1. Apache server-status

    1.找到apache配置文件:httpd.conf   2.打开模块: LoadModule status_module modules/mod_status.so   3.在文件末尾处加上以下代码: ...

  2. Android中基于Socket的网络通信

    1. Socket介绍 2. ServerSocket的建立与使用 3. 使用ServerSocket建立聊天服务器-1 4. 使用ServerSocket建立聊天服务器-2 5. 在Android中 ...

  3. PYTHON开发--面向对象基础二

    一.成员修饰符 共有成员 私有成员, __字段名 - 无法直接访问,只能间接访问 1.     私有成员 1.1  普通方法种的私有成员 class Foo: def __init__(self, n ...

  4. #Leet Code# Root to leaf

    语言:Python 描述:使用递归实现 def getList(self, node): if node is None: return [] if node.left is None and nod ...

  5. 苹果搜索广告后台大揭秘,最全最细致详解,手把手设置教程「后附官方视频」-b

    WWDC2016 搜索广告分会视频和 PPT 发布了,ASO100 带开发者第一时间了解 Search Ads 后台设置(文末有原声视频). 首先介绍一下搜索广告的模式和竞价规则 广告模式为 CPT( ...

  6. SQL Server日期函数之获得一个月中的天数

    SQL Server日期函数之获得一个月中的天数在实际中的应用比例还是占为多数的,如果你对这一技术,心存好奇的话,以下的文章将会揭开它的神秘面纱,望会在以后的学习或是工作中带来很大的帮助. 获得一个月 ...

  7. 对比JQuery与JavaScript

    JavaScript 是一种脚本语言,主要用在浏览器中,实现对网页的文档对象的操作和一些用户交互动作的处理.而 jQuery 则是 JavaScript 的一个代码库(或习惯性叫类库),它将一些在 J ...

  8. BZOJ 2301: [HAOI2011]Problem b 莫比乌斯反演

    2301: [HAOI2011]Problem b Time Limit: 50 Sec  Memory Limit: 256 MBSubmit: 1007  Solved: 415[Submit][ ...

  9. HDUOJ Clear All of Them I 状压DP

    Clear All of Them I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 122768/62768 K (Java/Oth ...

  10. Java中getAttribute getParameter 区别

            网上说的不少,发现都是同一篇,汗..... (1)HttpServletRequest类有setAttribute()方法,而没有setParameter()方法 (2)当两个Web组 ...