Lucene、Elasticsearch、Kibana 入門教程和環境搭建 信息檢索模型 Lucene Elasticsearch 問題總結 Github 地址 公衆號

信息檢索模型

信息檢索模型最重要的概念就是倒排索引,倒排索引是搜索引擎中常見的索引方法,用來存儲在全文搜索下某個單詞在一個文檔中存儲位置的映射。通過倒排索引,我們輸入一個關鍵詞,可以非常快地獲取包含這個關鍵詞的文檔列表。

Lucene

Lucene 是一個基於 Java 的全文信息檢索工具包,它不是一個完整的搜索應用程序,而是爲你的應用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一個開源項目。也是目前最爲流行的基於 Java 開源全文檢索工具包。

Elasticsearch 就是基於 Lucene 的。

demo

官網:https://lucene.apache.org/core/downloads.html

可以使用國內鏡像下載:https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/java/8.5.2/lucene-8.5.2.zip

下面是 Java 代碼的簡單例子,Maven 配置文件:

<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-core</artifactId>
    <version>8.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-queryparser</artifactId>
    <version>8.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-common</artifactId>
    <version>8.5.2</version>
</dependency>
<dependency>
    <groupId>org.apache.lucene</groupId>
    <artifactId>lucene-analyzers-smartcn</artifactId>
    <version>8.5.2</version>
</dependency>

測試分詞

注意下面使用的 SmartChineseAnalyzer 是包 lucene-analyzers-smartcn。

@Test
public void testAnalyzer() throws IOException {
    String chinese = "中華人民共和國簡稱中國,是一個有13億人口的國家";
    Analyzer analyzer = new SmartChineseAnalyzer();
    TokenStream tokenStream = analyzer.tokenStream(chinese, new StringReader(chinese));
    tokenStream.reset();
    CharTermAttribute attribute = tokenStream.getAttribute(CharTermAttribute.class);
    System.out.println("分詞結果:");
    while (tokenStream.incrementToken()) {
        System.out.print(attribute.toString() + "|");
    }
    analyzer.close();
}

程序輸出:

分詞結果:
中華人民共和國|簡稱|中國|是|一個|有|13|億|人口|的|國家|

測試索引

下面程序創建了3個包含 id, title, content 的文檔,其中每個類型都是 FieldType,使用 SmartChineseAnalyzer。索引目錄是 web 根目錄下的 indexDir 文件夾。

@Test
public void testIndex() throws IOException {
    List<String> titleList = Lists.newArrayList("中國房企洛杉磯醜聞:百萬美元行賄案遭曝光", "2025年之前美國不會退出WTO了",
            "特朗普退出總統競選?");
    List<String> contentList = Lists.newArrayList(
            "據調查,惠澤爾從中國房企手裏收取了超過150萬美元的現金賄賂,合人民幣超過1000萬元。",
            "美國特朗普政府上臺以來,每隔幾月,便總要傳出有關“美國要退出世貿組織(WTO)”的消息。那麼究竟美國能不能退出WTO?",
            "“共和黨的操盤手首次提出了這樣的可能性”,即川普總統可能會退出2020年總統競選");

    Path indexPath = Paths.get("indexDir");
    Directory dir = FSDirectory.open(indexPath);

    // 設置新聞ID索引並存儲
    FieldType idType = new FieldType();
    idType.setIndexOptions(IndexOptions.DOCS);
    idType.setStored(true);

    // 設置新聞標題索引文檔、此項頻率、位移信息、偏移量,存儲並詞條化
    FieldType titleType = new FieldType();
    titleType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
    titleType.setStored(true);
    titleType.setTokenized(true);

    FieldType contentType = new FieldType();
    contentType.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
    contentType.setStored(true);
    contentType.setTokenized(true);
    contentType.setStoreTermVectors(true);
    contentType.setStoreTermVectorOffsets(true);
    contentType.setStoreTermVectorPayloads(true);
    contentType.setStoreTermVectorPositions(true);

    Analyzer analyzer = new SmartChineseAnalyzer();
    IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
    indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
    IndexWriter indexWriter = new IndexWriter(dir, indexWriterConfig);

    for (int i = 0; i < titleList.size(); i++) {
        Document doc = new Document();
        doc.add(new Field("id", Integer.toString(i + 1), idType));
        doc.add(new Field("title", titleList.get(i), titleType));
        doc.add(new Field("content", contentList.get(i), contentType));
        indexWriter.addDocument(doc);
    }
    indexWriter.commit();
    indexWriter.close();
    dir.close();
}

在運行代碼後,會在根目錄生成 indexDir 文件夾(代碼中指定),如下圖所示。

搜索索引

@Test
public void testSearch() throws Exception {
    Path indexPath = Paths.get("indexDir");
    Directory dir = FSDirectory.open(indexPath);
    IndexReader reader = DirectoryReader.open(dir);
    IndexSearcher searcher = new IndexSearcher(reader);
    Analyzer analyzer = new SmartChineseAnalyzer();
    QueryParser parser = new QueryParser("title", analyzer);
    parser.setDefaultOperator(QueryParser.Operator.AND);
    Query query = parser.parse("房企");
    System.out.println("query : " + query.toString());

    TopDocs topDocs = searcher.search(query, 10);
    for (ScoreDoc sd : topDocs.scoreDocs) {
        Document doc = searcher.doc(sd.doc);
        System.out.println("docId: " + sd.doc);
        System.out.println("id: " + doc.get("id"));
        System.out.println("title: " + doc.get("title"));
        System.out.println("content: " + doc.get("content"));
        System.out.println("文檔評分:" + sd.score);
    }
}

會搜出關於房企的信息。

Elasticsearch

全文搜索屬於最常見的需求,開源的 Elasticsearch (以下簡稱 Elastic)是目前全文搜索引擎的首選。

它可以快速地儲存、搜索和分析海量數據。維基百科、Stack Overflow、Github 都採用它。

Elastic 的底層是開源庫 Lucene。但是,你沒法直接用 Lucene,必須自己寫代碼去調用它的接口。Elastic 是 Lucene 的封裝,提供了 REST API 的操作接口,開箱即用。

入門教程推薦阮一峯的《全文搜索引擎 Elasticsearch 入門教程》

官網是:https://www.elastic.co/cn/,上面內容很全面,感覺直接看官網最好。

Elastic Stack

原來是通過 Logstash 進行日誌收集與解析,Elasticsearch 作爲搜索引擎,Kibana 作爲可視化分析平臺。但是 Logstash 有CPU和內存性能問題,官方開發了 Beats 數據採集工具。本文通過一個例子使用 Java 直接向 Elasticsearch 發送消息,並搭建 Kibana 數據可視化查詢。

Docker 搭建 Elasticsearch

說明:經過試驗,本文不使用最新版本,而是使用 Elasticsearch 6.8.4 版本,因爲 Spring boot data 2.3 集成的版本就是 6.8.4,同時 Kibana 也要和 Elasticsearch 版本完全一致,否則會出各種奇葩問題。

docker 拉取 6.8.4 版本鏡像:

docker pull elasticsearch:6.8.4

啓動鏡像:

docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:6.8.4

這裏使用簡單模式,9200是 HTTP rest 協議,9300 是 tcp 協議。啓動完成後,可以在瀏覽器中輸入網址 0.0.0.0:9200,返回一下內容說明啓動成功:

{
    "name": "_jhdsik",
    "cluster_name": "docker-cluster",
    "cluster_uuid": "mpaTnrRaSY2_e3LFPz4QXw",
    "version": {
    "number": "6.8.4",
    "build_flavor": "default",
    "build_type": "docker",
    "build_hash": "bca0c8d",
    "build_date": "2019-10-16T06:19:49.319352Z",
    "build_snapshot": false,
    "lucene_version": "7.7.2",
    "minimum_wire_compatibility_version": "5.6.0",
    "minimum_index_compatibility_version": "5.0.0"
    },
    "tagline": "You Know, for Search"
}

如果要看 log,可以使用命令:

docker logs -f 44afc4738685

其中 44afc4738685 是 CONTAINER ID(可通過 docker ps 查看)。

修改 Elasticsearch 配置:

docker exec -it epic_beaver /bin/bash

其中 epic_beaver 是我的 docker Elasticsearch 容器名稱。進入 config 目錄修改 elasticsearch.yml 文件。

cluster.name: "docker-cluster"
network.host: 0.0.0.0
# xpack.security.enabled: true

其中 xpack.security.enabled 在設置密碼時使用,暫時不做設置。

修改完配置文件,重啓容器。

Docker 搭建 Kibana

由於 Elasticsearch 使用的是 6.8.4 版本,Kibana 也要使用這個版本。

docker 下載 Kibana 鏡像:

docker pull kebana:6.8.4

修改配置文件(本例是 /root/etc/kibana.yml)

server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://0.0.0.0:9200" ]
# xpack.monitoring.ui.container.elasticsearch.enabled: true

注:這裏的 elasticsearch.hosts 配置跟 docker 網絡模式有關,因爲 elasticsearch 和 kibana 是 2 個獨立的 docker 容器,直接設置 http://0.0.0.0:9200 可能不通,需要額外配置。本例使用的是阿里雲的服務器,配置成了公網 IP,配置中改成了 0.0.0.0,讀者需要自行替換。

docker 啓動 Kibana:

docker run -d --restart=always --log-driver json-file --log-opt max-size=100m --log-opt max-file=2 --name kibana -p 5601:5601 -v /root/etc/kibana.yml:/usr/share/kibana/config/kibana.yml kibana:6.8.4

在瀏覽器輸入 0.0.0.0:5601,如果出現下面的界面,則表示啓動成功。

啓動失敗可以通過 docker logs -f kibana_container_id 查看日誌。

demo

本例使用 Spring boot,Maven pom 引入:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

在 application.yml 中增加:

  data:
    elasticsearch:
      cluster-nodes: 0.0.0.0:9300
      cluster-name: docker-cluster

寫一個實體類 EsNewsEntity,indexName 設置爲 news_test:

@Data
@Builder
@Document(indexName = "news_test")
public class EsNewsEntity {

    @Id
    private String id;
    private String title;
    private String content;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private Date time;
}

整體查詢使用 ElasticsearchRepository,裏面有 CRUD 的操作,還有分頁和排序,Spring data 使各種數據查詢有了統一的操作接口,使用起來也很方便。

本例中只是想 ES 中簡單插入一些數據,repository 如下:

public interface EsNewsRepository extends ElasticsearchRepository<EsNewsEntity, String> {
}

插入數據的代碼如下,其中插入了2條 title 爲 “韓國男星” 的文章,1條 title 爲 “特大暴雨”的文章:

@Test
public void testEs() {
    EsNewsEntity newsEntity = EsNewsEntity.builder().id("1").title("韓國男星")
            .content("我很喜歡大神、金鐘國、光洙").time(DateTime.now().toDate()).build();
    esNewsRepository.save(newsEntity);

    newsEntity = EsNewsEntity.builder().id("2").title("特大暴雨")
            .content("特大暴雨夜襲四川冕寧:山洪摧毀村莊 一家5口遇難").time(DateTime.now().toDate()).build();
    esNewsRepository.save(newsEntity);

    newsEntity = EsNewsEntity.builder().id("3").title("韓國男星")
            .content("韓國男星身材管理多嚴格?金秀賢 Rain有八塊腹肌").time(DateTime.now().toDate()).build();
    esNewsRepository.save(newsEntity);

    System.out.println("es save ...");
}

運行之後,在 Kibana->Management->Create index pattern,輸入上面 News 的 indexName:news_test,設置 Date 爲時間索引。

之後刷新頁面,可以根據時間搜索新插入的幾條數據。

問題總結

  1. 本例的 es 和 Kibana 沒有使用密碼登錄,在實際應用過程中 es 可以限制 ip 訪問。我在研究的過程中,設置好 es 和 Kibana 的密碼後,沒法使用 Spring data 直接與 es 通信,暫時放棄。
  2. es 和 Kibana 的版本一定要一致!
  3. spring-boot-starter-data-elasticsearch 的 es 版本並不高,最新版本已經是 7.8,但是 spring-boot-starter-data-elasticsearch 集成的仍然是 6.8,需要注意。如果必須使用最新版,需要額外配置。
  4. Kibana 在分析日誌、數據分析時很強大。
  5. 本示例是 demo 演示,不要在生產環境中使用。
  6. 可以直接在騰訊雲、阿里雲上購買 es 服務,不過真心貴……小站點或個人開發者還是自行搭建比較划算。
  7. 《Lucene Elasticsearch 全文檢索實戰》這本書不建議買,書的內容淺顯,排版和內容問題很多。比如有的代碼分隔符是中文標點,書的前幾章代碼是深色背景,後幾章代碼沒有背景……

Github 地址

Github地址

公衆號

coding 筆記、點滴記錄,以後的文章也會同步到公衆號(Coding Insight)中,希望大家關注_

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章