使用 Java 開(kāi)源工具建立一個(gè)靈活的搜索引擎【轉(zhuǎn)摘】

          仇 寅 (qiuyin04@software.nju.edu.cn)南京大學(xué)

          2007 年 11 月 27 日

          為應(yīng)用程序添加搜索能力經(jīng)常是一個(gè)常見(jiàn)的需求。本文介紹了一個(gè)框架,開(kāi)發(fā)者可以使用它以最小的付出實(shí)現(xiàn)搜索引擎功能,理想情況下只需要一個(gè)配置文件。該框架基于若干開(kāi)源的庫(kù)和工具,如 Apache Lucene,Spring 框架,cpdetector 等。它支持多種資源。其中兩個(gè)典型的例子是數(shù)據(jù)庫(kù)資源和文件系統(tǒng)資源。Indexer 對(duì)配置的資源進(jìn)行索引并傳輸?shù)街醒敕?wù)器,之后這些索引可以通過(guò) API 進(jìn)行搜索。Spring 風(fēng)格的配置文件允許清晰靈活的自定義和調(diào)整。核心 API 也提供了可擴(kuò)展的接口。

          引言

                 為應(yīng)用程序添加搜索能力經(jīng)常是一個(gè)常見(jiàn)的需求。盡管已經(jīng)有若干程序庫(kù)提供了對(duì)搜索基礎(chǔ)設(shè)施的支持,然而對(duì)于很多人而言,使用它們從頭開(kāi)始建立一個(gè)搜索引擎將是一個(gè)付出不小而且可能乏味的過(guò)程。另一方面,很多的小型應(yīng)用對(duì)于搜索功能的需求和應(yīng)用場(chǎng)景具有很大的相似性。本文試圖以對(duì)多數(shù)小型應(yīng)用的適用性為出發(fā)點(diǎn),用 Java 語(yǔ)言構(gòu)建一個(gè)靈活的搜索引擎框架。使用這個(gè)框架,多數(shù)情形下可以以最小的付出建立起一個(gè)搜索引擎。最理想的情況下,甚至只需要一個(gè)配置文件。特殊的情形下,可以通過(guò)靈活地對(duì)框架進(jìn)行擴(kuò)展?jié)M足需求。當(dāng)然,如題所述,這都是借助開(kāi)源工具的力量。





          基礎(chǔ)知識(shí)

                  Apache Lucene 是開(kāi)發(fā)搜索類(lèi)應(yīng)用程序時(shí)最常用的 Java 類(lèi)庫(kù),我們的框架也將基于它。為了下文更好的描述,我們需要先了解一些有關(guān) Lucene 和搜索的基礎(chǔ)知識(shí)。注意,本文不關(guān)注索引的文件格式、分詞技術(shù)等話(huà)題。

          什么是搜索和索引

                  從用戶(hù)的角度來(lái)看,搜索的過(guò)程是通過(guò)關(guān)鍵字在某種資源中尋找特定的內(nèi)容的過(guò)程。而從計(jì)算機(jī)的角度來(lái)看,實(shí)現(xiàn)這個(gè)過(guò)程可以有兩種辦法。一是對(duì)所有資源逐個(gè)與關(guān)鍵字匹配,返回所有滿(mǎn)足匹配的內(nèi)容;二是如同字典一樣事先建立一個(gè)對(duì)應(yīng)表,把關(guān)鍵字與資源的內(nèi)容對(duì)應(yīng)起來(lái),搜索時(shí)直接查找這個(gè)表即可。顯而易見(jiàn),第二個(gè)辦法效率要高得多。建立這個(gè)對(duì)應(yīng)表事實(shí)上就是建立逆向索引(inverted index)的過(guò)程。

          Lucene 基本概念

                  Lucene 是 Doug Cutting 用 Java 開(kāi)發(fā)的用于全文搜索的工具庫(kù)。在這里,我假設(shè)讀者對(duì)其已有基本的了解,我們只對(duì)一些重要的概念簡(jiǎn)要介紹。要深入了解可以參考 參考資源 中列出的相關(guān)文章和圖書(shū)。下面這些是 Lucene 里比較重要的類(lèi)。

          • DE>DocumentDE>:索引包含多個(gè) DE>DocumentDE>。而每個(gè) DE>DocumentDE> 則包含多個(gè) DE>FieldDE> 對(duì)象。DE>DocumentDE> 可以是從數(shù)據(jù)庫(kù)表里取出的一堆數(shù)據(jù),可以是一個(gè)文件,也可以是一個(gè)網(wǎng)頁(yè)等。注意,它不等同于文件系統(tǒng)中的文件。
          • DE>FieldDE>:一個(gè) DE>FieldDE> 有一個(gè)名稱(chēng),它對(duì)應(yīng) DE>DocumentDE>的一部分?jǐn)?shù)據(jù),表示文檔的內(nèi)容或者文檔的元數(shù)據(jù)(與下文中提到的資源元數(shù)據(jù)不是一個(gè)概念)。一個(gè) DE>FieldDE> 對(duì)象有兩個(gè)重要屬性:Store ( 可以有 YES, NO, COMPACT 三種取值 ) 和 Index ( 可以有 TOKENIZED, UN_TOKENIZED, NO, NO_NORMS 四種取值 )
          • DE>QueryDE>:抽象了搜索時(shí)使用的語(yǔ)句。
          • DE>IndexSearcherDE>:提供 DE>QueryDE> 對(duì)象給它,它利用已有的索引進(jìn)行搜索并返回搜索結(jié)果。
          • DE>HitsDE>:一個(gè)容器,包含了指向一部分搜索結(jié)果的指針。

                  使用 Lucene 來(lái)進(jìn)行編制索引的過(guò)程大致為:將輸入的數(shù)據(jù)源統(tǒng)一為字符串或者文本流的形式,然后從數(shù)據(jù)源提取數(shù)據(jù),創(chuàng)建合適的 DE>FieldDE> 添加到對(duì)應(yīng)數(shù)據(jù)源的 DE>DocumentDE> 對(duì)象之中。



          系統(tǒng)概覽

                  要建立一個(gè)通用的框架,必須對(duì)不同情況的共性進(jìn)行抽象。反映到設(shè)計(jì)需要注意兩點(diǎn)。一是要提供擴(kuò)展接口;二是要盡量降低模塊之間的耦合程度。我們的框架很簡(jiǎn)單地分為兩個(gè)模塊:索引模塊和搜索模塊。索引模塊在不同的機(jī)器上各自進(jìn)行對(duì)資源的索引,并把索引文件(事實(shí)上,下面我們會(huì)說(shuō)到,還有元數(shù)據(jù))統(tǒng)一傳輸?shù)酵粋€(gè)地方(可以是在遠(yuǎn)程服務(wù)器上,也可以是在本地)。搜索模塊則利用這些從多個(gè)索引模塊收集到的數(shù)據(jù)完成用戶(hù)的搜索請(qǐng)求。

                  圖 1 展現(xiàn)了整體的框架。可以看到,兩個(gè)模塊之間相對(duì)是獨(dú)立的,它們之間的關(guān)聯(lián)不是通過(guò)代碼,而是通過(guò)索引和元數(shù)據(jù)。在下文中,我們將會(huì)詳細(xì)介紹如何基于開(kāi)源工具設(shè)計(jì)和實(shí)現(xiàn)這兩個(gè)模塊。


          圖 1. 系統(tǒng)架構(gòu)圖

          圖 1. 系統(tǒng)架構(gòu)圖





           

          建立索引

                  可以進(jìn)行索引的對(duì)象有很多,如文件、網(wǎng)頁(yè)、RSS Feed 等。在我們的框架中,我們定義可以進(jìn)行索引的一類(lèi)對(duì)象為資源。從實(shí)現(xiàn)細(xì)節(jié)上來(lái)說(shuō),從一個(gè)資源中可以提取出多個(gè) DE>DocumentDE> 對(duì)象。文件系統(tǒng)資源和數(shù)據(jù)庫(kù)結(jié)果集資源都是資源的代表性例子。

                  前面提到,從資源中收集到的索引被統(tǒng)一傳送到同一個(gè)地方,以被搜索模塊所用。顯然除了索引之外,搜索模塊需要對(duì)資源有更多的了解,如資源的名稱(chēng)、搜索該資源后搜索結(jié)果的呈現(xiàn)格式等。這些額外的附加信息稱(chēng)為資源的元數(shù)據(jù)。元數(shù)據(jù)和索引數(shù)據(jù)一同被收集起來(lái),放置到某個(gè)特定的位置。

                  簡(jiǎn)要地介紹過(guò)資源的概念之后,我們首先為其定義一個(gè) DE>ResourceDE> 接口。這個(gè)接口的聲明如下。


              清單 1. Resource 接口
          public interface Resource {
                      // RequestProcessor 對(duì)象被動(dòng)地從資源中提取 Document,并返回提取的數(shù)量
                      public int extractDocuments(ResourceProcessor processor);
                      // 添加的 DocumentListener 將在每一個(gè) Document 對(duì)象被提取出時(shí)被調(diào)用
                      public void addDocumentListener(DocumentListener l);
                      // 返回資源的元數(shù)據(jù)
                      public ResourceMetaData getMetaData();
                      }

                  其中元數(shù)據(jù)包含的字段見(jiàn)下表。在下文中,我們還會(huì)對(duì)元數(shù)據(jù)的用途做更多的介紹。


                  表 1. 資源元數(shù)據(jù)包含的字段
          屬性 類(lèi)型 含義
          resourceName String 資源的唯一名稱(chēng)
          resourceDescription String 資源的介紹性文字
          hitTextPattern String 當(dāng)文檔被搜索到時(shí),這個(gè) pattern 規(guī)定了結(jié)果顯示的格式
          searchableFields String[] 可以被搜索的字段名稱(chēng)

                  而 DE>DocumentListenerDE> 的代碼如下。


              清單 2. DocumentListener 接口
          public interface DocumentListener extends EventListener {
                      public void documentExtracted(Document doc);
                      }
                      

                  為了讓索引模塊能夠知道所有需要被索引的資源,我們?cè)谶@里使用 Spring 風(fēng)格的 XML 文件配置索引模塊中的所有組件,尤其是所有資源。您可以在 下載部分 查看一個(gè)示例配置文件。

          為什么選擇使用 Spring 風(fēng)格的配置文件?

          這主要有兩個(gè)好處:

          • 僅依賴(lài)于 Spring Core 和 Spring Beans 便免去了定義配置機(jī)制和解析配置文件的負(fù)擔(dān);
          • Spring 的 IoC 機(jī)制降低了框架的耦合性,并使擴(kuò)展框架變得簡(jiǎn)單;

           

                  基于以上內(nèi)容,我們可以大致描述出索引模塊工作的過(guò)程:     

          1. 首先在 XML 配置的 bean 中找出所有 DE>ResourceDE> 對(duì)象;
          2. 對(duì)每一個(gè)調(diào)用其 DE>extractDocuments()DE> 方法,這一步除了完成對(duì)資源的索引外,還會(huì)在每次提取出一個(gè) DE>DocumentDE> 對(duì)象之后,通知注冊(cè)在該資源上的所有 DE>DocumentListenerDE>;
          3. 接著處理資源的元數(shù)據(jù)(DE>getMetaData()DE> 的返回值);
          4. 將緩存里的數(shù)據(jù)寫(xiě)入到本地磁盤(pán)或者傳送給遠(yuǎn)程服務(wù)器;

           

                  在這個(gè)過(guò)程中,有兩個(gè)地方值得注意。

                  第一,對(duì)資源可以注冊(cè) DE>DocumentListenerDE> 使得我們可以在運(yùn)行時(shí)刻對(duì)索引過(guò)程有更為動(dòng)態(tài)的控制。舉一個(gè)簡(jiǎn)單例子,對(duì)某個(gè)文章發(fā)布站點(diǎn)的文章進(jìn)行索引時(shí),一個(gè)很正常的要求便是發(fā)布時(shí)間更靠近當(dāng)前時(shí)間的文章需要在搜索結(jié)果中排在靠前的位置。每篇文章顯然對(duì)應(yīng)一個(gè) DE>DocumentDE> 對(duì)象,在 Lucene 中我們可以通過(guò)設(shè)置 DE>DocumentDE> 的 DE>boostDE> 值來(lái)對(duì)其進(jìn)行加權(quán)。假設(shè)其中文章發(fā)布時(shí)間的 DE>FieldDE> 的名稱(chēng)為 DE>PUB_TIMEDE>,那么我們可以為資源注冊(cè)一個(gè) DE>DocumentListenerDE>,當(dāng)它被通知時(shí),則檢測(cè) DE>PUB_TIMEDE> 的值,根據(jù)距離當(dāng)前時(shí)間的遠(yuǎn)近進(jìn)行加權(quán)。

                  第二點(diǎn)很顯然,在這個(gè)過(guò)程中,DE>extractDocuments()DE> 方法的實(shí)現(xiàn)依不同類(lèi)型的資源而各異。下面我們主要討論兩種類(lèi)型的資源:文件系統(tǒng)資源和數(shù)據(jù)庫(kù)結(jié)果集資源。這兩個(gè)類(lèi)都實(shí)現(xiàn)了上面的 DE>接口DE>。

              文件系統(tǒng)資源

                  對(duì)文件系統(tǒng)資源的索引通常從一個(gè)基目錄開(kāi)始,遞歸處理每個(gè)需要進(jìn)行索引的文件。該資源有一個(gè)字符串?dāng)?shù)組類(lèi)型的 DE>excludedFilesDE> 屬性,表示在處理文件時(shí)需要排除的文件絕對(duì)路徑的正則表達(dá)式。在遞歸遍歷文件系統(tǒng)樹(shù)的同時(shí),絕對(duì)路徑匹配 DE>excludedFilesDE> 中任意一項(xiàng)的文件將不會(huì)被處理。這主要是考慮到一般我們只需要對(duì)一部分文件夾(比如排除可能存在的備份目錄)中的一部分文件(如 doc, ppt 文件等)進(jìn)行索引。

                  除了所有文件共有的文件名、文件路徑、文件大小和修改時(shí)間等 Field,不同類(lèi)型的文件需要有不同的處理方法。為了保留靈活性,我們使用 Strategy 模式封裝對(duì)不同類(lèi)型文件的處理方式。為此我們抽象出一個(gè) DE>DocumentBuilderDE> 的接口,該接口僅定義了一個(gè)方法如下:


              清單 3. DocumentBuilder 接口
          public interface DocumentBuilder {
                      Document buildDocument(InputStream is);
                      }

          什么是 Strategy 模式?

          根據(jù) Design patterns: Elements of reusable object orientated software 一書(shū):Strategy 模式“定義一系列的算法,把它們分別封裝起來(lái),并且使它們相互可以替換。這個(gè)模式使得算法可以獨(dú)立于使用它的客戶(hù)而變化。”

                  不同的 DE>DocumentBuilderDE>(Strategy) 用于從一個(gè)輸入流中讀取數(shù)據(jù),處理不同類(lèi)型的文件。對(duì)于常見(jiàn)的文件格式來(lái)說(shuō),都有合適的開(kāi)源工具幫助進(jìn)行解析。在下表中我們列舉一些常見(jiàn)文件類(lèi)型的解析辦法。

          文件類(lèi)型 常用擴(kuò)展名 可以使用的解析辦法
          純文本文檔 txt 無(wú)需類(lèi)庫(kù)解析
          RTF 文檔 rtf 使用 DE>javax.swing.text.rtf.RTFEditorKitDE> 類(lèi)
          Word 文檔(非 OOXML 格式) doc Apache POI (可配合使用 POI Scratchpad)
          PowerPoint 演示文稿(非 OOXML 格式) xls Apache POI (可配合使用 POI Scratchpad)
          PDF 文檔 pdf PDFBox(可能中文支持欠佳)
          HTML 文檔 htm, html JTidy, Cobra

                  這里以 Word 文件為例,給出一個(gè)簡(jiǎn)單的參考實(shí)現(xiàn)。


              清單 4. 解析純文本內(nèi)容的實(shí)現(xiàn)
          // WordDocument 是 Apache POI Scratchpad 中的一個(gè)類(lèi)
                      Document buildDocument(InputStream is) {
                      String bodyText = null;
                      try {
                      WordDocument wordDoc = new WordDocument(is);
                      StringWriter sw = new StringWriter();
                      wordDoc.writeAllText(sw);
                      sw.close();
                      bodyText = sw.toString();
                      } catch (Exception e) {
                      throw new DocumentHandlerException("Cannot extract text from a Word document", e);
                      }
                      if ((bodyText != null) && (bodyText.trim().length() > 0)) {
                      Document doc = new Document();
                      doc.add(new Field("body", bodyText, Field.Store.YES, Field.Index.TOKENIZED));
                      return doc;
                      }
                      return null;
                      }
                      

                  那么如何選擇合適的 Strategy 來(lái)處理文件呢?UNIX 系統(tǒng)下的 file(1) 工具提供了從 magicnumber 獲取文件類(lèi)型的功能,我們可以使用 DE>Runtime.exec()DE> 方法調(diào)用這一命令。但這需要在有 file(1) 命令的情況下,而且并不能識(shí)別出所有文件類(lèi)型。在一般的情況下我們可以簡(jiǎn)單地根據(jù)擴(kuò)展名來(lái)使用合適的類(lèi)處理文件。擴(kuò)展名和類(lèi)的映射關(guān)系寫(xiě)在 properties 文件中。當(dāng)需要添加對(duì)新的文件類(lèi)型的支持時(shí),我們只需添加一個(gè)新的實(shí)現(xiàn) DE>DocumentBuilderDE> 接口的類(lèi),并在映射文件中添加一個(gè)映射關(guān)系即可。

              數(shù)據(jù)庫(kù)結(jié)果集資源

                  大多數(shù)應(yīng)用使用數(shù)據(jù)庫(kù)作為永久存儲(chǔ),對(duì)數(shù)據(jù)庫(kù)查詢(xún)結(jié)果集索引是一個(gè)常見(jiàn)需求。

                  生成一個(gè)數(shù)據(jù)庫(kù)結(jié)果集資源的實(shí)例需要先提供一個(gè)查詢(xún)語(yǔ)句,然后執(zhí)行查詢(xún),得到一個(gè)結(jié)果集。這個(gè)結(jié)果集中的內(nèi)容便是我們需要進(jìn)行索引的對(duì)象。DE>extractDocumentsDE> 的實(shí)現(xiàn)便是為結(jié)果集中的每一行創(chuàng)建一個(gè) DE>DocumentDE> 對(duì)象。和文件系統(tǒng)資源不同的是,數(shù)據(jù)庫(kù)資源需要放入 DE>DocumentDE> 中的 DE>FieldDE> 一般都存在在查詢(xún)結(jié)果集之中。比如一個(gè)簡(jiǎn)單的文章發(fā)布站點(diǎn),對(duì)其后臺(tái)數(shù)據(jù)庫(kù)執(zhí)行查詢(xún) DE>SELECT ID, TITLE, CONTENT FROM ARTICLEDE> 返回一個(gè)有三列的結(jié)果集。對(duì)結(jié)果集的每一行都會(huì)被提取出一個(gè) DE>DocumentDE> 對(duì)象,其中包含三個(gè) DE>FieldDE>,分別對(duì)應(yīng)這三列。

                  然而不同 DE>FieldDE> 的類(lèi)型是不同的。比如 DE>IDDE> 字段一般對(duì)應(yīng) DE>Store.YESDE> 和 DE>Index.NODE> 的 DE>FieldDE>;而 DE>TITLEDE> 字段則一般對(duì)應(yīng) DE>Store.YESDE> 和 DE>Index.TOKENIZEDDE> 的 DE>FieldDE>。為了解決這個(gè)問(wèn)題,我們?cè)跀?shù)據(jù)庫(kù)結(jié)果集資源的實(shí)現(xiàn)中提供一個(gè)類(lèi)型為 DE>PropertiesDE> 的 DE>fieldTypeMappingsDE> 屬性,用于設(shè)置數(shù)據(jù)庫(kù)字段所對(duì)應(yīng)的 DE>FieldDE> 的類(lèi)型。對(duì)于前面的情況來(lái)說(shuō),這個(gè)屬性可能會(huì)被配置成類(lèi)似這樣的形式:

          ID = YES, NO
                      TITLE = YES, TOKENIZED
                      CONTENT = NO, TOKENIZED

                  配合這個(gè)映射,我們便可以生成合適類(lèi)型的 DE>FieldDE>,完成對(duì)結(jié)果集索引的工作。





              收集索引

                  完成對(duì)資源的索引之后,還需要讓索引為搜索模塊所用。前面我們已經(jīng)說(shuō)過(guò)這里介紹的框架主要用于小型應(yīng)用,考慮到復(fù)雜性,我們采取簡(jiǎn)單地將分布在各個(gè)機(jī)器上的索引匯總到一個(gè)地方的策略。

                  匯總索引的傳輸方式可以有很多方案,比如使用 FTP、HTTP、rsync 等。甚至索引模塊和搜索模塊可以位于同一臺(tái)機(jī)器上,這種情況下只需要將索引進(jìn)行本地拷貝即可。同前面類(lèi)似,我們定義一個(gè) DE>TransporterDE> 接口。


              清單 5. Transporter 接口
          public interface Transporter {
                      public void transport(File file);
                      }

                  以 FTP 方式傳輸為例,我們使用 Commons Net 完成傳輸?shù)牟僮鳌?/p>

          public void transport(File file) throws TransportException {
                      FTPClient client = new FTPClient();
                      client.connect(host);
                      client.login(username, password);
                      client.changeWorkingDirectory(remotePath);
                      transportRecursive(client, file);
                      client.disconnect();
                      }
                      public void transportRecursive(FTPClient client, File file) {
                      if (file.isFile() && file.canRead()) {
                      client.storeFile(file.getName(), new FileInputStream(file));
                      } else if (file.isDirectory()) {
                      client.makeDirectory(file.getName());
                      client.changeWorkingDirectory(file.getName());
                      File[] fileList = file.listFiles();
                      for (File f : fileList) {
                      transportRecursive(client, f);
                      }
                      }
                      }
                      

                  對(duì)其他傳輸方案也有各自的方案進(jìn)行處理,具體使用哪個(gè) DE>TransporterDE> 的實(shí)現(xiàn)被配置在 Spring 風(fēng)格的索引模塊配置文件中。傳輸?shù)姆绞绞庆`活的。比如當(dāng)需要強(qiáng)調(diào)安全性時(shí),我們可以換用基于 SSL 的 FTP 進(jìn)行傳輸。所需要做的只是開(kāi)發(fā)一個(gè)使用 FTP over SSL 的 DE>TransporterDE> 實(shí)現(xiàn),并在配置文件中更改 DE>TransporterDE> 的實(shí)現(xiàn)即可。





              進(jìn)行搜索

                  在做了這么多之后,我們開(kāi)始接觸和用戶(hù)關(guān)聯(lián)最為緊密的搜索模塊。注意,我們的框架不包括一個(gè)搜索的 Web 前端界面。但是類(lèi)似這樣的界面可以在搜索模塊的基礎(chǔ)上方便地開(kāi)發(fā)出來(lái)。基于已經(jīng)收集好的索引進(jìn)行搜索是個(gè)很簡(jiǎn)單的過(guò)程。Lucene 已經(jīng)提供了功能強(qiáng)大的 DE>IndexSearcherDE> 及其子類(lèi)。在這個(gè)部分,我們不會(huì)再介紹如何使用這些類(lèi),而是關(guān)注在前文提到過(guò)的資源元數(shù)據(jù)上。元數(shù)據(jù)從各個(gè)資源所在的文件夾中讀取得到,它在搜索模塊中扮演重要的角色。

              構(gòu)建一個(gè)查詢(xún)

                  對(duì)不同資源進(jìn)行搜索的查詢(xún)方法并不一樣。例如搜索一個(gè)論壇里的所有留言時(shí),我們關(guān)注的一般是留言的標(biāo)題、作者和內(nèi)容;而當(dāng)搜索一個(gè) FTP 站點(diǎn)時(shí),我們更多關(guān)注的是文件名和文件內(nèi)容。另一方面,我們有時(shí)可能會(huì)使用一個(gè)查詢(xún)?nèi)ニ阉鞫鄠€(gè)資源的結(jié)果。這正是之前我們?cè)谇懊嫠岬降脑獢?shù)據(jù)中 DE>searchableFieldsDE> DE>resourceNameDE> 屬性的作用。前者指出一個(gè)資源中哪些字段是參與搜索的;后者則用于在搜索時(shí)確定使用哪個(gè)或者哪些索引。從技術(shù)細(xì)節(jié)來(lái)說(shuō),只有有了這些信息,我們才可以構(gòu)造出可用的 DE>QueryDE> 對(duì)象。

              呈現(xiàn)搜索結(jié)果

                  當(dāng)從 DE>IndexSearcherDE> 對(duì)象得到搜索結(jié)果(DE>HitsDE>)之后,當(dāng)然我們可以直接從中獲取需要的值,再格式化予以輸出。但一來(lái)格式化輸出搜索結(jié)果(尤其在 Web 應(yīng)用中)是個(gè)很常見(jiàn)的需求,可能會(huì)經(jīng)常變更;二來(lái)結(jié)果的呈現(xiàn)格式應(yīng)該是由分散的資源各自定義,而不是交由搜索模塊來(lái)定義。基于上面兩個(gè)原因,我們的框架將使用在資源收集端配置結(jié)果輸出格式的方式。這個(gè)格式由資源元數(shù)據(jù)中的 DE>hitTextPatternDE> 屬性定義。該屬性是一個(gè)字符串類(lèi)型的值,支持兩種語(yǔ)法

          • 形如 DE>${field_name}DE> 的子字符串都會(huì)被動(dòng)態(tài)替換成查詢(xún)結(jié)果中各個(gè) DE>DocumentDE> 內(nèi) DE>FieldDE> 的值。
          • 形如 DE>$function(...) DE>的被解釋為函數(shù),括號(hào)內(nèi)以逗號(hào)隔開(kāi)的符號(hào)都被解釋成參數(shù),函數(shù)可以嵌套。

                  例如搜索“具體”返回的搜索結(jié)果中包含一個(gè) DE>DocumentDE> 對(duì)象,其 DE>FieldDE> 如下表:

           

          Field 名稱(chēng) Field 內(nèi)容
          url http://example.org/article/1.html
          title 示例標(biāo)題
          content 這里是具體的內(nèi)容。

                  那么如果 DE>hitTextPattenDE> 被設(shè)置為“DE><a href="${url}">${title}</a><br/>$highlight(${content}, 5, "<b>", "</b>")DE>”,返回的結(jié)果經(jīng)瀏覽器解釋后可能的顯示結(jié)果如下(這只是個(gè)演示鏈接,請(qǐng)不要點(diǎn)擊):

              示例標(biāo)題 
              這里是具體...

                  上面提到的 DE>$highlight()DE> 函數(shù)用于在搜索結(jié)果中取得最匹配的一段文本,并高亮顯示搜索時(shí)使用的短語(yǔ),其第一個(gè)參數(shù)是高亮顯示的文本,第二個(gè)參數(shù)是顯示的文本長(zhǎng)度,第三和第四個(gè)參數(shù)是高亮文本時(shí)使用的前綴和后綴。

                  可以使用正則表達(dá)式和文本解析來(lái)實(shí)現(xiàn)前面所提到的語(yǔ)法。我們也可以使用 JavaCC 定義 DE>hitTextPatternDE> 的文法,進(jìn)而生成詞法分析器和語(yǔ)法解析器。這是更為系統(tǒng)并且相對(duì)而言不易出錯(cuò)的方法。對(duì) JavaCC 的介紹不是本文的重點(diǎn),您可以在下面的 閱讀資源 中找到學(xué)習(xí)資料。





              相關(guān)產(chǎn)品

                  下面列出的是一些與我們所提出的框架所相關(guān)或者類(lèi)似的產(chǎn)品,您可以在 學(xué)習(xí)資料 中更多地了解他們。

              IBM®OmniFind?Family

                  OmniFind 是 IBM 公司推出的企業(yè)級(jí)搜索解決方案。基于 UIMA (Unstructured Information Management Architecture) 技術(shù),它提供了強(qiáng)大的索引和獲取信息功能,支持巨大數(shù)量、多種類(lèi)型的文檔資源(無(wú)論是結(jié)構(gòu)化還是非結(jié)構(gòu)化),并為 Lotus®Domino®和 WebSphere®Portal 專(zhuān)門(mén)進(jìn)行了優(yōu)化。

              Apache Solr

                  Solr 是 Apache 的一個(gè)企業(yè)級(jí)的全文檢索項(xiàng)目,實(shí)現(xiàn)了一個(gè)基于 HTTP 的搜索服務(wù)器,支持多種資源和 Web 界面管理,它同樣建立在 Lucene 之上,并對(duì) Lucene 做了很多擴(kuò)展,例如支持動(dòng)態(tài)字段及唯一鍵,對(duì)查詢(xún)結(jié)果進(jìn)行動(dòng)態(tài)分組和過(guò)濾等。

              Google SiteSearch

                  使用 Google 的站點(diǎn)搜索功能可以方便而快捷地建立一個(gè)站內(nèi)搜索引擎。但是 Google 的站點(diǎn)搜索基于 Google 的網(wǎng)絡(luò)爬蟲(chóng),所以無(wú)法訪(fǎng)問(wèn)受保護(hù)的站點(diǎn)內(nèi)容或者 Intranet 上的資源。另外,Google 所支持的資源類(lèi)型也是有限的,我們無(wú)法對(duì)其進(jìn)行擴(kuò)展。

              SearchBlox?

                  SearchBlox 是一個(gè)商業(yè)的搜索引擎構(gòu)建框架。它本身是一個(gè) J2EE 組件,和我們的框架類(lèi)似,也支持對(duì)網(wǎng)頁(yè)和文件系統(tǒng)等資源進(jìn)行索引,進(jìn)而進(jìn)行搜索。





              還需考慮的問(wèn)題

                  本文介紹的思想試圖利用開(kāi)源的工具解決中小型應(yīng)用中的常見(jiàn)問(wèn)題。當(dāng)然,作為一個(gè)框架,它還有很多不足,下面列舉出一些可以進(jìn)行改進(jìn)的地方。

              性能考慮

                  當(dāng)需要進(jìn)行索引的資源數(shù)目不多時(shí),隔一定的時(shí)間進(jìn)行一次完全索引不會(huì)占用很長(zhǎng)時(shí)間。使用一臺(tái) 2G 內(nèi)存,Xeon 2.66G 處理器的服務(wù)器進(jìn)行實(shí)際測(cè)試,發(fā)現(xiàn)對(duì)數(shù)據(jù)庫(kù)資源的索引占用的時(shí)間很少,一千多條記錄花費(fèi)的時(shí)間在 1 秒到 2 秒之內(nèi)。而對(duì) 1400 多個(gè)文件進(jìn)行索引耗時(shí)大約十幾秒。但在大型應(yīng)用中,資源的容量是巨大的,如果每次都進(jìn)行完整的索引,耗費(fèi)的時(shí)間會(huì)很驚人。我們可以通過(guò)跳過(guò)已經(jīng)索引的資源內(nèi)容,刪除已不存在的資源內(nèi)容的索引,并進(jìn)行增量索引來(lái)解決這個(gè)問(wèn)題。這可能會(huì)涉及文件校驗(yàn)和索引刪除等。

                  另一方面,框架可以提供查詢(xún)緩存來(lái)提高查詢(xún)效率。框架可以在內(nèi)存中建立一級(jí)緩存,并使用如 OSCacheEHCache 實(shí)現(xiàn)磁盤(pán)上的二級(jí)緩存。當(dāng)索引的內(nèi)容變化不頻繁時(shí),使用查詢(xún)緩存更會(huì)明顯地提高查詢(xún)速度、降低資源消耗。

              分布式索引

                  我們的框架可以將索引分布在多臺(tái)機(jī)器上。搜索資源時(shí),查詢(xún)被 flood 到各個(gè)機(jī)器上從而獲得搜索結(jié)果。這樣可以免去傳輸索引到某一臺(tái)中央服務(wù)器的過(guò)程。當(dāng)然也可以基于實(shí)現(xiàn)了分布式哈希表 (DHT)的結(jié)構(gòu)化 P2P 網(wǎng)絡(luò),配合索引復(fù)制 (Replication),使得應(yīng)用程序更為安全,可靠,有伸縮性。在 閱讀資料 中給出了 一篇關(guān)于構(gòu)建分布式環(huán)境下全文搜索的可行性的論文。

              安全性

                  目前我們的框架并沒(méi)有涉及到安全性。除了依賴(lài)資源本身的訪(fǎng)問(wèn)控制(如受保護(hù)的網(wǎng)頁(yè)和文件系統(tǒng)等)之外,我們還可以從兩方面增強(qiáng)框架本身的安全性:

          1. 考慮到一個(gè)組織的搜索功能對(duì)不同用戶(hù)的權(quán)限設(shè)置不一定一樣,可以支持對(duì)用戶(hù)角色的定義,實(shí)行對(duì)搜索模塊的訪(fǎng)問(wèn)控制。
          2. 在資源索引模塊中實(shí)現(xiàn)一種機(jī)制,讓資源可以限制自己暴露的內(nèi)容,從而縮小索引模塊的索引范圍。這可以類(lèi)比 robots 文件可以規(guī)定搜索引擎爬蟲(chóng)的行為。

           





              總結(jié)

                  通過(guò)上文的介紹,我們認(rèn)識(shí)了一個(gè)可擴(kuò)展的框架,由索引模塊和搜索模塊兩部分組成。它可以靈活地適應(yīng)不同的應(yīng)用場(chǎng)景。如果需要更獨(dú)特的需求,框架本身預(yù)留了可以擴(kuò)展的接口,我們可以通過(guò)實(shí)現(xiàn)這些接口完成功能的定制。更重要的是這一切都是建立在開(kāi)源軟件的基礎(chǔ)之上。希望本文能為您揭示開(kāi)源的力量,體驗(yàn)用開(kāi)源工具組裝您自己的解決方案所帶來(lái)的莫大快樂(lè)。

          posted on 2009-09-18 23:39 張平輝 閱讀(262) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): JAVA

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導(dǎo)航

          統(tǒng)計(jì)

          留言簿

          文章分類(lèi)

          文章檔案

          搜索

          最新評(píng)論

          主站蜘蛛池模板: 读书| 澄城县| 彝良县| 郑州市| 永川市| 霞浦县| 台南市| 十堰市| 涿州市| 泸水县| 徐水县| 政和县| 芦山县| 囊谦县| 北宁市| 广水市| 延安市| 峨眉山市| 广平县| 建德市| 汉寿县| 双桥区| 珠海市| 铜梁县| 晋江市| 招远市| 巴林左旗| 庆元县| 浮山县| 邯郸市| 安康市| 秦安县| 淮南市| 临潭县| 宁乡县| 平乡县| 舟山市| 壤塘县| 平安县| 西乌珠穆沁旗| 图片|