1. 程式人生 > >海量搜尋服務架構搭建1-基於spring的搜尋服務

海量搜尋服務架構搭建1-基於spring的搜尋服務

相當於一個百度搜索系統

幾個名詞解釋

  • Lucene簡介

    • 1.什麼是lucene?
      Lucene是一個全文搜尋框架,而不是應用產品。因此它並不像http://www.baidu.com/ 或者google Desktop那麼拿來就能用,它只是提供了一種工具讓你能實現這些產品。
    • 2.lucene能做什麼?
      要回答這個問題,先要了解lucene的本質。實際上lucene的功能很單一,說到底,就是你給它若干個字串,然後它為你提供一個全文搜尋服務,告訴你你要搜尋的關鍵詞出現在哪裡。知道了這個本質,你就可以發揮想象做任何符合這個條件的事情了。你可以把站內新聞都索引了,做個資料庫;你可以把一個數據庫表的若干個欄位索引起來,那就不用再擔心因為“%like%”而鎖表了;你也可以寫個自己的搜尋引擎……
    • 3.你該不該選擇lucene
      下面給出一些測試資料,如果你覺得可以接受,那麼可以選擇。
      測試一:250萬記錄,300M左右文字,生成索引380M左右,800執行緒下平均處理時間300ms。
      測試二:37000記錄,索引資料庫中的兩個varchar欄位,索引檔案2.6M,800執行緒下平均處理時間1.5ms。
    • 4.lucene為什麼這麼快
      • 倒排索引
      • 壓縮演算法
      • 二元搜尋
    • 4.1 倒排索引
      根據屬性的值來查詢記錄。這種索引表中的每一項都包括一個屬性值和具有該屬性值的各記錄的地址。由於不是由記錄來確定屬性值,而是由屬性值來確定記錄的位置,因而稱為倒排索引(invertedindex)
      單詞——文件矩陣
    • 5.lucene的工作方式
      lucene提供的服務實際包含兩部分:一入一出。所謂入是寫入,即將你提供的源(本質是字串)寫入索引或者將其從索引中刪除;所謂出是讀出,即向用戶提供全文搜尋服務,讓使用者可以通過關鍵詞定位源
      • 寫入流程
        源字串首先經過analyzer處理,包括:分詞,分成一個個單詞;去除stopword(可選)。將源中需要的資訊加入Document的各個Field中,並把需要索引的Field索引起來,把需要儲存的Field儲存起來。將索引寫入儲存器,儲存器可以是記憶體或磁碟。
      • 讀出流程
        使用者提供搜尋關鍵詞,經過analyzer處理。對處理後的關鍵詞搜尋索引找出對應的Document。使用者根據需要從找到的Document中提取需要的Field。
  • 第一步,建立索引和查詢索引

    • IDEA建立webapp工程,匯入lucene響應jar包
      匯入5個lucene包
    • 建立兩個目錄:文章資源目錄、索引檔案目錄
    • 文章資源目錄可以直接爬取wget -o /tmp/wget.log -P /root/data --no-parent --no-verbose -m -D www.bjsxt.com -N --convert-links --random-wait -A html,HTML http://www.bjsxt.com

      兩個檔案目錄
    • 兩個函式:建立索引CreateIndex、查詢索引SearchIndex
    • 測試結果
      結果
package com.sxt.lucene;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
/**
 * @author: ZouTai
 * @date: 2018/3/28
 * @description: 建立索引
 */

public class CreateIndex {

    // 靜態變數,資源位置
    static String dataDir = "E:/JavaEE_IJ_WorkSpace/lucene/Data/data";
    static String indexDir = "E:/JavaEE_IJ_WorkSpace/lucene/Data/index";

    @Test
    public void createIndex() {
        try {
            // 檔案和分析器
            Directory dir = FSDirectory.open(new File(indexDir));
            Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_4_9);

            // 寫入索引配置
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_4_9, analyzer);
            indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
            IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);

            // 遍歷檔案
            File file = new File(dataDir);
            File[] files = file.listFiles();

            for(File f : files) {
                Document document = new Document();
                // 檔名、內容、最後修改時間
                document.add(new StringField("filename", f.getName(), Field.Store.YES));
                document.add(new TextField("content", FileUtils.readFileToString(f), Field.Store.YES));
                document.add(new LongField("lastModify", f.lastModified(), Field.Store.YES));
                indexWriter.addDocument(document);
            }
            indexWriter.close();

        } catch (IOException e) {
            e.printStackTrace();
        }


    }

}
package com.sxt.lucene;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;

import java.io.File;

/**
 * @author: ZouTai
 * @date: 2018/3/29
 * @description: 查詢索引
 */
public class SearchIndex {

    @Test
    public void searchIndex() {

        try {
            Directory directory = FSDirectory.open(new File(CreateIndex.indexDir));
            IndexReader indexReader = DirectoryReader.open(directory);
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);

            Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_4_9);
            QueryParser queryParser = new QueryParser(Version.LUCENE_4_9, "content", analyzer);
            Query query = queryParser.parse("form");

            TopDocs topDocs = indexSearcher.search(query, 10);
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            for (ScoreDoc sd : scoreDocs) {
                int docId = sd.doc;
                Document document = indexReader.document(docId);
                System.out.println(document.get("filename"));

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 中文分詞的支援
    • 目前有11大中文分詞器,本文使用的是IKAnalyzer。
    • 中文分詞器不同於英文單詞的單個獨立性,需要對多義詞單詞進行識別,這歌就需要基於統計學來判斷,比如“請把手拿開”中的“把手”,既可以作為自個名字,也可以作為動詞,這裡作為動詞,所以,有時候,需要通過上下文或者廣泛的概率來判斷,那個是最可能的。
    • 導包:目前的包管理情況
      jar包
    • 構建SpringMVC專案結構:
      • 匯入Springmvc的8個包:spring7+jstl
      • 配置xml檔案:web.xml和spring-servlet.xml
        enter description here
    • 編寫相應的mvc的java檔案
    • 這裡注意幾個問題:
      • webapp轉web,需要配置tomcat容器參考
      • 啟動報錯Intellij idea 出現錯誤 error:java: 無效的源發行版: 8spring3.2對應為jdk1.7
      • (拒絕訪問。):資原始檔被佔用
      • 流程
        流程圖
        效果圖