ElasticSearch研究

前言

​ ES相關技術文檔,很久之前看的,一門技術時間長不去研究就會容易忘了,應有些小夥伴的要求希望我做一期ES技術專欄,我就把以前看過的相關文檔整理整理,給大家分享下。

1 ElasticSearch介紹

1.1 介紹

官方網址:https://www.elastic.co/cn/products/elasticsearch

Github:https://github.com/elastic/elasticsearch

總結:

1、elasticsearch是一個基於Lucene的高擴展的分佈式搜索服務器,支持開箱即用。

2、elasticsearch隱藏了Lucene的複雜性,對外提供Restful 接口來操作索引、搜索。

突出優點:

1.擴展性好,可部署上百臺服務器集羣,處理PB級數據。

2.近實時的去索引數據、搜索數據。

es和solr選擇哪個?

1.如果你公司現在用的solr可以滿足需求就不要換了。

2.如果你公司準備進行全文檢索項目的開發,建議優先考慮elasticsearch,因爲像Github這樣大規模的搜索都在用它。

1.2原理與應用

1.2.1索引結構

​ 下圖是ElasticSearch的索引結構,下邊黑色部分是物理結構,上邊黃色部分是邏輯結構,邏輯結構也是爲了更好的去描述ElasticSearch的工作原理及去使用物理結構中的索引文件。

邏輯結構部分是一個倒排索引表:

1、將要搜索的文檔內容分詞,所有不重複的詞組成分詞列表。

2、將搜索的文檔最終以Document方式存儲起來。

3、每個詞和docment都有關聯。

如下:

現在,如果我們想搜索 quick brown ,我們只需要查找包含每個詞條的文檔:

​ 兩個文檔都匹配,但是第一個文檔比第二個匹配度更高。如果我們使用僅計算匹配詞條數量的簡單 相似性算法 ,那麼,我們可以說,對於我們查詢的相關性來講,第一個文檔比第二個文檔更佳。

1.2.3 RESTful應用方法

如何使用es?

Elasticsearch提供 RESTful Api接口進行索引、搜索,並且支持多種客戶端。

下圖是es在項目中的應用方式:

1)用戶在前端搜索關鍵字

2)項目前端通過http方式請求項目服務端

3)項目服務端通過Http RESTful方式請求ES集羣進行搜索

4)ES集羣從索引庫檢索數據。

2 ElasticaSearch安裝

2.1 安裝

安裝配置:

1、新版本要求至少jdk1.8以上。

2、支持tar、zip、rpm等多種安裝方式。

在windows下開發建議使用ZIP安裝方式。

3、支持docker方式安裝

詳細參見:https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html

下載ES: Elasticsearch 6.2.1

https://www.elastic.co/downloads/past-releases

解壓 elasticsearch-6.2.1.zip

bin:腳本目錄,包括:啓動、停止等可執行腳本

config:配置文件目錄

data:索引目錄,存放索引文件的地方

logs:日誌目錄

modules:模塊目錄,包括了es的功能模塊

plugins :插件目錄,es支持插件機制

2.2 配置文件

2.2.1 三個配置文件

ES的配置文件的地址根據安裝形式的不同而不同:

使用zip、tar安裝,配置文件的地址在安裝目錄的config下。

使用RPM安裝,配置文件在/etc/elasticsearch下。

使用MSI安裝,配置文件的地址在安裝目錄的config下,並且會自動將config目錄地址寫入環境變量ES_PATH_CONF。

本教程使用的zip包安裝,配置文件在ES安裝目錄的config下。

配置文件如下:

elasticsearch.yml : 用於配置Elasticsearch運行參數 jvm.options : 用於配置Elasticsearch JVM設置 log4j2.properties: 用於配置Elasticsearch日誌

2.2.2 elasticsearch.yml

配置格式是YAML,可以採用如下兩種方式:

方式1:層次方式

path: data: /var/lib/elasticsearch logs: /var/log/elasticsearch

方式2:屬性方式

​ path.data: /var/lib/elasticsearch path.logs: /var/log/elasticsearch

本項目採用方式2,例子如下:

cluster.name: xuecheng
node.name: xc_node_1
network.host: 0.0.0.0
http.port: 9200
transport.tcp.port: 9300
node.master: true
node.data: true
#discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301", "0.0.0.0:9302"]
discovery.zen.minimum_master_nodes: 1
bootstrap.memory_lock: false
node.max_local_storage_nodes: 1

path.data: D:\ElasticSearch\elasticsearch-6.2.1\data
path.logs: D:\ElasticSearch\elasticsearch-6.2.1\logs

http.cors.enabled: true
http.cors.allow-origin: /.*/

注意path.data和path.logs路徑配置正確。

常用的配置項如下:

cluster.name:

​ 配置elasticsearch的集羣名稱,默認是elasticsearch。建議修改成一個有意義的名稱。

node.name:

​ 節點名,通常一臺物理服務器就是一個節點,es會默認隨機指定一個名字,建議指定一個有意義的名稱,方便管理

​ 一個或多個節點組成一個cluster集羣,集羣是一個邏輯的概念,節點是物理概念,後邊章節會詳細介紹。

path.conf: 設置配置文件的存儲路徑,tar或zip包安裝默認在es根目錄下的config文件夾,rpm安裝默認在/etc/ elasticsearch path.data: 設置索引數據的存儲路徑,默認是es根目錄下的data文件夾,可以設置多個存儲路徑,用逗號隔開。 path.logs: 設置日誌文件的存儲路徑,默認是es根目錄下的logs文件夾 path.plugins: 設置插件的存放路徑,默認是es根目錄下的plugins文件夾

bootstrap.memory_lock: true 設置爲true可以鎖住ES使用的內存,避免內存與swap分區交換數據。 network.host: 設置綁定主機的ip地址,設置爲0.0.0.0表示綁定任何ip,允許外網訪問,生產環境建議設置爲具體的ip。 http.port: 9200 設置對外服務的http端口,默認爲9200。

transport.tcp.port: 9300 集羣結點之間通信端口

node.master: 指定該節點是否有資格被選舉成爲master結點,默認是true,如果原來的master宕機會重新選舉新的master。 node.data: 指定該節點是否存儲索引數據,默認爲true。

discovery.zen.ping.unicast.hosts: ["host1:port", "host2:port", "..."] 設置集羣中master節點的初始列表。

discovery.zen.ping.timeout: 3s 設置ES自動發現節點連接超時的時間,默認爲3秒,如果網絡延遲高可設置大些。 discovery.zen.minimum_master_nodes:

​ 主結點數量的最少值 ,此值的公式爲:(master_eligible_nodes / 2) + 1 ,比如:有3個符合要求的主結點,那麼這裏要設置爲2。

node.max_local_storage_nodes:

​ 單機允許的最大存儲結點數,通常單機啓動一個結點建議設置爲1,開發環境如果單機啓動多個節點可設置大於1.

2.2.3 jvm.options

設置最小及最大的JVM堆內存大小:

在jvm.options中設置 -Xms和-Xmx:

1) 兩個值設置爲相等

2) 將Xmx 設置爲不超過物理內存的一半。

2.2.4 log4j2.properties

日誌文件設置,ES使用log4j,注意日誌級別的配置。

2.2.5 系統配置

在linux上根據系統資源情況,可將每個進程最多允許打開的文件數設置大些。

su limit -n 查詢當前文件數

使用命令設置limit:

先切換到root,設置完成再切回elasticsearch用戶。

sudo su  
ulimit -n 65536 
su elasticsearch 

也可通過下邊的方式修改文件進行持久設置

/etc/security/limits.conf

將下邊的行加入此文件:

elasticsearch  -  nofile  65536

2.3 啓動ES

進入bin目錄,在cmd下運行:elasticsearch.bat

瀏覽器輸入:http://localhost:9200

顯示結果如下(配置不同內容則不同)說明ES啓動成功:

{
  "name" : "xc_node_1",
  "cluster_name" : "xuecheng",
  "cluster_uuid" : "J18wPybJREyx1kjOoH8T-g",
  "version" : {
    "number" : "6.2.1",
    "build_hash" : "7299dc3",
    "build_date" : "2018-02-07T19:34:26.990113Z",
    "build_snapshot" : false,
    "lucene_version" : "7.2.1",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

2.4 head插件安裝

​ head插件是ES的一個可視化管理插件,用來監視ES的狀態,並通過head客戶端和ES服務進行交互,比如創建映射、創建索引等,head的項目地址在https://github.com/mobz/elasticsearch-head 。

從ES6.0開始,head插件支持使得node.js運行。

1、安裝node.js

2、下載head並運行

git clone git://github.com/mobz/elasticsearch-head.git cd elasticsearch-head npm install npm run start open HTTP://本地主機:9100 /

3、運行

打開瀏覽器調試工具發現報錯:

Origin null is not allowed by Access-Control-Allow-Origin.

原因是:head插件作爲客戶端要連接ES服務(localhost:9200),此時存在跨域問題,elasticsearch默認不允許跨域訪問。

解決方案:

設置elasticsearch允許跨域訪問。

在config/elasticsearch.yml 後面增加以下參數:

#開啓cors跨域訪問支持,默認爲false http.cors.enabled: true #跨域訪問允許的域名地址,(允許所有域名)以上使用正則 http.cors.allow-origin: /.*/

注意:將config/elasticsearch.yml另存爲utf-8編碼格式。

成功連接ES

3 ES快速入門

​ ES作爲一個索引及搜索服務,對外提供豐富的REST接口,快速入門部分的實例使用head插件來測試,目的是對ES的使用方法及流程有個初步的認識。

3.1 創建索引庫

​ ES的索引庫是一個邏輯概念,它包括了分詞列表及文檔列表,同一個索引庫中存儲了相同類型的文檔。它就相當於MySQL中的表,或相當於Mongodb中的集合。

關於索引這個語:

索引(名詞):ES是基於Lucene構建的一個搜索服務,它要從索引庫搜索符合條件索引數據。

索引(動詞):索引庫剛創建起來是空的,將數據添加到索引庫的過程稱爲索引。

下邊介紹兩種創建索引庫的方法,它們的工作原理是相同的,都是客戶端向ES服務發送命令。

1)使用postman或curl這樣的工具創建:

put http://localhost:9200/索引庫名稱

{
  "settings":{
  "index":{
      "number_of_shards":1,
      "number_of_replicas":0
    }  
  }
}

number_of_shards:設置分片的數量,在集羣中通常設置多個分片,表示一個索引庫將拆分成多片分別存儲不同的結點,提高了ES的處理能力和高可用性,入門程序使用單機環境,這裏設置爲1。

number_of_replicas:設置副本的數量,設置副本是爲了提高ES的高可靠性,單機環境設置爲0.

如下是創建的例子,創建xc_course索引庫,共1個分片,0個副本:

2)使用head插件創建

效果如下:

3.2 創建映射

3.2.1 概念說明

​ 在索引中每個文檔都包括了一個或多個field,創建映射就是向索引庫中創建field的過程,下邊是document和field與關係數據庫的概念的類比:

​ 文檔(Document)----------------Row記錄

​ 字段(Field)-------------------Columns 列

​ 注意:6.0之前的版本有type(類型)概念,type相當於關係數據庫的表,ES官方將在ES9.0版本中徹底刪除type。

上邊講的創建索引庫相當於關係數據庫中的數據庫還是表?

1、如果相當於數據庫就表示一個索引庫可以創建很多不同類型的文檔,這在ES中也是允許的。

2、如果相當於表就表示一個索引庫只能存儲相同類型的文檔,ES官方建議 在一個索引庫中只存儲相同類型的文檔。

3.2.2 創建映射

我們要把課程信息存儲到ES中,這裏我們創建課程信息的映射,先來一個簡單的映射,如下:

發送:post http://localhost:9200/索引庫名稱/類型名稱/_mapping

創建類型爲xc_course的映射,共包括三個字段:name、description、studymondel

由於ES6.0版本還沒有將type徹底刪除,所以暫時把type起一個沒有特殊意義的名字。

post 請求:http://localhost:9200/xc_course/doc/_mapping

表示:在xc_course索引庫下的doc類型下創建映射。doc是類型名,可以自定義,在ES6.0中要弱化類型的概念,給它起一個沒有具體業務意義的名稱。

 {
    "properties": {
           "name": {
              "type": "text"
           },
           "description": {
              "type": "text"
           },
           "studymodel": {
              "type": "keyword"
           }
        }
}

映射創建成功,查看head界面:

3.3 創建文檔

ES中的文檔相當於MySQL數據庫表中的記錄。

發送:put 或Post http://localhost:9200/xc_course/doc/id值

(如果不指定id值ES會自動生成ID)

http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000

{
  "name":"Bootstrap開發框架",
  "description":"Bootstrap是由Twitter推出的一個前臺頁面開發框架,在行業之中使用較爲廣泛。此開發框架包含了大量的CSS、JS程序代碼,可以幫助開發者(尤其是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。",
  "studymodel":"201001"
}

使用postman測試:

通過head查詢數據:

3.4 搜索文檔

1、根據課程id查詢文檔

發送:get http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000

使用postman測試:

2、查詢所有記錄

發送 get http://localhost:9200/xc_course/doc/_search

3、查詢名稱中包括spring 關鍵字的的記錄

發送:get http://localhost:9200/xc_course/doc/_search?q=name:bootstrap

4、查詢學習模式爲201001的記錄

發送 get http://localhost:9200/xc_course/doc/_search?q=studymodel:201001

3.4.1查詢結果分析

分析上邊查詢結果:

{
    "took": 1,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 0.2876821,
        "hits": [
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "4028e58161bcf7f40161bcf8b77c0000",
                "_score": 0.2876821,
                "_source": {
                    "name": "Bootstrap開發框架",
                    "description": "Bootstrap是由Twitter推出的一個前臺頁面開發框架,在行業之中使用較爲廣泛。此開發框架包含了大量的CSS、JS程序代碼,可以幫助開發者(尤其是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。",
                    "studymodel": "201001"
                }
            }
        ]
    }
}

took:本次操作花費的時間,單位爲毫秒。

timed_out:請求是否超時

_shards:說明本次操作共搜索了哪些分片

hits:搜索命中的記錄

hits.total : 符合條件的文檔總數 hits.hits :匹配度較高的前N個文檔

hits.max_score:文檔匹配得分,這裏爲最高分

_score:每個文檔都有一個匹配度得分,按照降序排列。

_source:顯示了文檔的原始內容。

4 IK分詞器

4.1測試分詞器

在添加文檔時會進行分詞,索引中存放的就是一個一個的詞(term),當你去搜索時就是拿關鍵字去匹配詞,最終找到詞關聯的文檔。

測試當前索引庫使用的分詞器:

post 發送:localhost:9200/_analyze

{"text":"測試分詞器,後邊是測試內容:spring cloud實戰"}

結果如下:

會發現分詞的效果將 “測試” 這個詞拆分成兩個單字“測”和“試”,這是因爲當前索引庫使用的分詞器對中文就是單字分詞。

4.2 安裝IK分詞器

使用IK分詞器可以實現對中文分詞的效果。

下載IK分詞器:(Github地址:https://github.com/medcl/elasticsearch-analysis-ik)

下載zip:

解壓,並將解壓的文件拷貝到ES安裝目錄的plugins下的ik目錄下

測試分詞效果:

發送:post localhost:9200/_analyze

{"text":"測試分詞器,後邊是測試內容:spring cloud實戰","analyzer":"ik_max_word" }

4.3 兩種分詞模式

ik分詞器有兩種分詞模式:ik_max_word和ik_smart模式。

1、ik_max_word

​ 會將文本做最細粒度的拆分,比如會將“中華人民共和國人民大會堂”拆分爲“中華人民共和國、中華人民、中華、華人、人民共和國、人民、共和國、大會堂、大會、會堂等詞語。

2、ik_smart

​ 會做最粗粒度的拆分,比如會將“中華人民共和國人民大會堂”拆分爲中華人民共和國、人民大會堂。

測試兩種分詞模式:

發送:post localhost:9200/_analyze

{"text":"中華人民共和國人民大會堂","analyzer":"ik_smart" }

4.4 自定義詞庫

如果要讓分詞器支持一些專有詞語,可以自定義詞庫。

iK分詞器自帶一個main.dic的文件,此文件爲詞庫文件。

在上邊的目錄中新建一個my.dic文件(注意文件格式爲utf-8(不要選擇utf-8 BOM))

可以在其中自定義詞彙:

比如定義:

配置文件中配置my.dic,

重啓ES,測試分詞效果:

發送:post localhost:9200/_analyze

{"text":"測試分詞器,後邊是測試內容:spring cloud實戰","analyzer":"ik_max_word" }

6 映射

​ 上邊章節安裝了ik分詞器,如果在索引和搜索時去使用ik分詞器呢?如何指定其它類型的field,比如日期類型、數值類型等。

本章節學習各種映射類型及映射維護方法。

6.1 映射維護方法

1、查詢所有索引的映射:

GET: http://localhost:9200/_mapping

2、創建映射

post 請求:http://localhost:9200/xc_course/doc/_mapping

一個例子:

 {
    "properties": {
           "name": {
              "type": "text"
           },
           "description": {
              "type": "text"
           },
           "studymodel": {
              "type": "keyword"
           }
        }
}

3、更新映射

映射創建成功可以添加新字段,已有字段不允許更新。

4、刪除映射

通過刪除索引來刪除映射。

6.2 常用映射類型

6.2.1 text文本字段

下圖是ES6.2核心的字段類型如下:

字符串包括text和keyword兩種類型:

1、text

1)analyzer

通過analyzer屬性指定分詞器。

下邊指定name的字段類型爲text,使用ik分詞器的ik_max_word分詞模式。

 "name": {
                  "type": "text",
                  "analyzer":"ik_max_word"
   }

上邊指定了analyzer是指在索引和搜索都使用ik_max_word,如果單獨想定義搜索時使用的分詞器則可以通過search_analyzer屬性。

對於ik分詞器建議是索引時使用ik_max_word將搜索內容進行細粒度分詞,搜索時使用ik_smart提高搜索精確性。

"name": {
                  "type": "text",
                  "analyzer":"ik_max_word",
                  "search_analyzer":"ik_smart"
   }

2)index

通過index屬性指定是否索引。

默認爲index=true,即要進行索引,只有進行索引纔可以從索引庫搜索到。

但是也有一些內容不需要索引,比如:商品圖片地址只被用來展示圖片,不進行搜索圖片,此時可以將index設置爲false。

刪除索引,重新創建映射,將pic的index設置爲false,嘗試根據pic去搜索,結果搜索不到數據

 "pic": {
            "type": "text",
              "index":false
           }

3)store

是否在source之外存儲,每個文檔索引後會在 ES中保存一份原始文檔,存放在"_source"中,一般情況下不需要設置store爲true,因爲在_source中已經有一份原始文檔了。

6.2.1.1 測試

刪除xc_course/doc下的映射

創建新映射:Post http://localhost:9200/xc_course/doc/_mapping

 {
    "properties": {
           "name": {
                  "type": "text",
                  "analyzer":"ik_max_word",
                  "search_analyzer":"ik_smart"
            },
           "description": {
              "type": "text",
              "analyzer":"ik_max_word",
              "search_analyzer":"ik_smart"
           },
           "pic":{
             "type":"text",
             "index":false
           },
           "studymodel":{
             "type":"text"
           }
    }
}

插入文檔:

http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000

{
  "name":"Bootstrap開發框架",
  "description":"Bootstrap是由Twitter推出的一個前臺頁面開發框架,在行業之中使用較爲廣泛。此開發框架包含了大量的CSS、JS程序代碼,可以幫助開發者(尤其是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。",
  "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
  "studymodel":"201002"
}

查詢測試:

Get http://localhost:9200/xc_course/_search?q=name:開發

Get http://localhost:9200/xc_course/_search?q=description:開發

Get http://localhost:9200/xc_course/_search?q=pic:group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg

Get http://localhost:9200/xc_course/_search?q=studymodel:201002

通過測試發現:name和description都支持全文檢索,pic不可作爲查詢條件。

6.2.2 keyword關鍵字字段

​ 上邊介紹的text文本字段在映射時要設置分詞器,keyword字段爲關鍵字字段,通常搜索keyword是按照整體搜索,所以創建keyword字段的索引時是不進行分詞的,比如:郵政編碼、手機號碼、身份證等。keyword字段通常用於過慮、排序、聚合等。

6.2.2.1測試

更改映射:

{
    "properties": {
           "studymodel":{
             "type":"keyword"
           },
            "name":{
             "type":"keyword"
           }
    }
}

插入文檔:

{
 "name": "java編程基礎",
 "description": "java語言是世界第一編程語言,在軟件開發領域使用人數最多。",
 "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
 "studymodel": "201001"
}

根據studymodel查詢文檔

搜索:http://localhost:9200/xc_course/_search?q=name:java

name是keyword類型,所以查詢方式是精確查詢。

6.2.3 date日期類型

日期類型不用設置分詞器。

通常日期類型的字段用於排序。

1)format

通過format設置日期格式

例子:

下邊的設置允許date字段存儲年月日時分秒、年月日及毫秒三種格式。

{
    "properties": {
        "timestamp": {
          "type":   "date",
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
        }
      }
}

插入文檔:

Post :http://localhost:9200/xc_course/doc/3

{
"name": "spring開發基礎",
"description": "spring 在java領域非常流行,java程序員都在用。",
"studymodel": "201001",
 "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
 "timestamp":"2018-07-04 18:28:58"
}

6.2.4 數值類型

下邊是ES支持的數值類型

1、儘量選擇範圍小的類型,提高搜索效率

2、對於浮點數儘量用比例因子,比如一個價格字段,單位爲元,我們將比例因子設置爲100這在ES中會按 分 存儲,映射如下:

 "price": {
        "type": "scaled_float",
        "scaling_factor": 100
  },

由於比例因子爲100,如果我們輸入的價格是23.45則ES中會將23.45乘以100存儲在ES中。

如果輸入的價格是23.456,ES會將23.456乘以100再取一個接近原始值的數,得出2346。

使用比例因子的好處是整型比浮點型更易壓縮,節省磁盤空間。

如果比例因子不適合,則從下表選擇範圍小的去用:

更新已有映射,並插入文檔:

http://localhost:9200/xc_course/doc/3

{
"name": "spring開發基礎",
"description": "spring 在java領域非常流行,java程序員都在用。",
"studymodel": "201001",
 "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
 "timestamp":"2018-07-04 18:28:58",
 "price":38.6
}

6.2.5 綜合例子

創建如下映射

post:http://localhost:9200/xc_course/doc/_mapping

{
                "properties": {
                    "description": {
                        "type": "text",
                        "analyzer": "ik_max_word",
                        "search_analyzer": "ik_smart"
                    },
                    "name": {
                        "type": "text",
                        "analyzer": "ik_max_word",
                        "search_analyzer": "ik_smart"
                    },
                    "pic":{
                        "type":"text",
                        "index":false
                    },
                    "price": {
                        "type": "float"
                    },
                    "studymodel": {
                        "type": "keyword"
                    },
                    "timestamp": {
                        "type": "date",
                        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
                    }
                }
            }

插入文檔:

Post: http://localhost:9200/xc_course/doc/1 { "name": "Bootstrap開發", "description": "Bootstrap是由Twitter推出的一個前臺頁面開發框架,是一個非常流行的開發框架,此框架集成了多種頁面效果。此開發框架包含了大量的CSS、JS程序代碼,可以幫助開發者(尤其是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。", "studymodel": "201002", "price":38.6, "timestamp":"2018-04-25 19:11:35", "pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg" }

6索引管理

6.1 搭建工程

6.1.1 ES客戶端

ES提供多種不同的客戶端:

1、TransportClient

ES提供的傳統客戶端,官方計劃8.0版本刪除此客戶端。

2、RestClient

RestClient是官方推薦使用的,它包括兩種:Java Low Level REST Client和 Java High Level REST Client。

ES在6.0之後提供 Java High Level REST Client, 兩種客戶端官方更推薦使用 Java High Level REST Client,不過當前它還處於完善中,有些功能還沒有。

本教程準備採用 Java High Level REST Client,如果它有不支持的功能,則使用Java Low Level REST Client。

添加依賴:

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.2.1</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>6.2.1</version>
</dependency>

6.1.2 創建搜索工程

創建搜索工程(maven工程):xc-service-search,添加RestHighLevelClient依賴及junit依賴。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>xc-framework-parent</artifactId>
        <groupId>com.xuecheng</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../xc-framework-parent/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>xc-service-search</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-framework-model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-framework-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.xuecheng</groupId>
            <artifactId>xc-service-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>6.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

</project>

2、配置文件

application.yml

server:
  port: ${port:40100}
spring:
  application:
    name: xc-search-service
xuecheng:
  elasticsearch:
    hostlist: ${eshostlist:127.0.0.1:9200} #多個結點中間用逗號分隔

3、配置類

創建com.xuecheng.search.config包

在其下創建配置類

package com.xuecheng.search.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ElasticsearchConfig {

    @Value("${xuecheng.elasticsearch.hostlist}")
    private String hostlist;

    @Bean
    public RestHighLevelClient restHighLevelClient(){
        //解析hostlist配置信息
        String[] split = hostlist.split(",");
        //創建HttpHost數組,其中存放es主機和端口的配置信息
        HttpHost[] httpHostArray = new HttpHost[split.length];
        for(int i=0;i<split.length;i++){
            String item = split[i];
            httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
        }
        //創建RestHighLevelClient客戶端
        return new RestHighLevelClient(RestClient.builder(httpHostArray));
    }

    //項目主要使用RestHighLevelClient,對於低級的客戶端暫時不用
    @Bean
    public RestClient restClient(){
        //解析hostlist配置信息
        String[] split = hostlist.split(",");
        //創建HttpHost數組,其中存放es主機和端口的配置信息
        HttpHost[] httpHostArray = new HttpHost[split.length];
        for(int i=0;i<split.length;i++){
            String item = split[i];
            httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
        }
        return RestClient.builder(httpHostArray).build();
    }

}

3、啓動類

@SpringBootApplication
@EntityScan("com.xuecheng.framework.domain.search")//掃描實體類
@ComponentScan(basePackages={"com.xuecheng.api"})//掃描接口
@ComponentScan(basePackages={"com.xuecheng.search"})//掃描本項目下的所有類
@ComponentScan(basePackages={"com.xuecheng.framework"})//掃描common下的所有類
public class SearchApplication {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SearchApplication.class, args);
    }

}

6.2創建索引庫

6.2.1 API

創建索引:

put http://localhost:9200/索引名稱

{
  "settings":{
  "index":{
      "number_of_shards":1,#分片的數量
      "number_of_replicas":0#副本數量
    }  
  }
}

創建映射:

發送:put http://localhost:9200/索引庫名稱/類型名稱/_mapping

創建類型爲xc_course的映射,共包括三個字段:name、description、studymodel

http://localhost:9200/xc_course/doc/_mapping

 {
    "properties": {
           "name": {
              "type": "text",
              "analyzer":"ik_max_word",
              "search_analyzer":"ik_smart"
           },
           "description": {
              "type": "text",
              "analyzer":"ik_max_word",
              "search_analyzer":"ik_smart"
           },
           "studymodel": {
              "type": "keyword"
           },
           "price": {
              "type": "float"
           },
           "timestamp": {
                "type":   "date",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
            }
        }
}

6.2.2 Java Client

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestIndex {

    @Autowired
    RestHighLevelClient client;

    @Autowired
    RestClient restClient;

   //創建索引庫
    @Test
    public void testCreateIndex() throws IOException {
        //創建索引請求對象,並設置索引名稱
        CreateIndexRequest createIndexRequest = new CreateIndexRequest("xc_course");
        //設置索引參數
        createIndexRequest.settings(Settings.builder().put("number_of_shards",1)
                                            .put("number_of_replicas",0));

        //設置映射
        createIndexRequest.mapping("doc"," {\n" +
                " \t\"properties\": {\n" +
                "           \"name\": {\n" +
                "              \"type\": \"text\",\n" +
                "              \"analyzer\":\"ik_max_word\",\n" +
                "              \"search_analyzer\":\"ik_smart\"\n" +
                "           },\n" +
                "           \"description\": {\n" +
                "              \"type\": \"text\",\n" +
                "              \"analyzer\":\"ik_max_word\",\n" +
                "              \"search_analyzer\":\"ik_smart\"\n" +
                "           },\n" +
                "           \"studymodel\": {\n" +
                "              \"type\": \"keyword\"\n" +
                "           },\n" +
                "           \"price\": {\n" +
                "              \"type\": \"float\"\n" +
                "           }\n" +
                "        }\n" +
                "}", XContentType.JSON);
        //創建索引操作客戶端
        IndicesClient indices = client.indices();

        //創建響應對象
        CreateIndexResponse createIndexResponse = indices.create(createIndexRequest);
        //得到響應結果
        boolean acknowledged = createIndexResponse.isAcknowledged();
        System.out.println(acknowledged);
    }
    //刪除索引庫
    @Test
    public void testDeleteIndex() throws IOException {
        //刪除索引請求對象
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("xc_course");
        //刪除索引
        DeleteIndexResponse deleteIndexResponse = client.indices().delete(deleteIndexRequest);
        //刪除索引響應結果
        boolean acknowledged = deleteIndexResponse.isAcknowledged();
        System.out.println(acknowledged);
    }
}

6.3 添加文檔

6.3.1 API

格式如下: PUT /{index}/{type}/{id} { "field": "value", ... }

如果不指定id,ES會自動生成。

一個例子:

put http://localhost:9200/xc_course/doc/3

 {
 "name":"spring cloud實戰",
 "description":"本課程主要從四個章節進行講解: 1.微服務架構入門 2.spring cloud 基礎入門 3.實戰Spring Boot 4.註冊中心eureka。",
 "studymodel":"201001"
 "price":5.6
 }

6.3.2 Java Client

 //添加文檔
    @Test
    public void testAddDoc() throws IOException {
       //準備json數據
        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("name", "spring cloud實戰");
        jsonMap.put("description", "本課程主要從四個章節進行講解: 1.微服務架構入門 2.spring cloud 基礎入門 3.實戰Spring Boot 4.註冊中心eureka。");
        jsonMap.put("studymodel", "201001");
        SimpleDateFormat dateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        jsonMap.put("timestamp", dateFormat.format(new Date()));
        jsonMap.put("price", 5.6f);
        //索引請求對象
        IndexRequest indexRequest = new IndexRequest("xc_course","doc");
        //指定索引文檔內容
        indexRequest.source(jsonMap);
        //索引響應對象
        IndexResponse indexResponse = client.index(indexRequest);
        //獲取響應結果
        DocWriteResponse.Result result = indexResponse.getResult();
        System.out.println(result);

    }

6.4 查詢文檔

6.4.1 API

格式如下: GET /{index}/{type}/{id}

6.4.2 Java Client

//查詢文檔
@Test
public void getDoc() throws IOException {
    GetRequest getRequest = new GetRequest(
            "xc_course",
            "doc",
            "4028e581617f945f01617f9dabc40000");
    GetResponse getResponse = client.get(getRequest);
    boolean exists = getResponse.isExists();
    Map<String, Object> sourceAsMap = getResponse.getSourceAsMap();
    System.out.println(sourceAsMap);
}

6.5 更新文檔

6.5.1 Api

ES更新文檔的順序是:先檢索到文檔、將原來的文檔標記爲刪除、創建新文檔、刪除舊文檔,創建新文檔就會重建索引。

通過請求Url有兩種方法:

1、完全替換

Post:http://localhost:9200/xc_test/doc/3

 {
 "name":"spring cloud實戰",
 "description":"本課程主要從四個章節進行講解: 1.微服務架構入門 2.spring cloud 基礎入門 3.實戰Spring Boot 4.註冊中心eureka。",
 "studymodel":"201001"
 "price":5.6
 }

2、局部更新

下邊的例子是隻更新price字段。

post: http://localhost:9200/xc_test/doc/3/_update

{
	"doc":{"price":66.6}
}

6.5.2 Java Client

使用 Client Api更新文檔的方法同上邊第二種局部更新方法。

可以指定文檔的部分字段也可以指定完整的文檔內容。

    //更新文檔
    @Test
    public void updateDoc() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("xc_course", "doc", "4028e581617f945f01617f9dabc40000");
        Map<String, String> map = new HashMap<>();
        map.put("name", "spring cloud實戰");
        updateRequest.doc(map);
        UpdateResponse update = client.update(updateRequest);
        RestStatus status = update.status();
        System.out.println(status);
    }

6.6 刪除文檔

6.6.1 Api

根據id刪除,格式如下:

DELETE /{index}/{type}/{id}

搜索匹配刪除,將搜索出來的記錄刪除,格式如下:

POST /{index}/{type}/_delete_by_query

下邊是搜索條件例子:

{
  "query":{
      "term":{
          "studymodel":"201001"
      }
  }
}

上邊例子的搜索匹配刪除會將studymodel爲201001的記錄全部刪除。

6.6.2 Java Client

//根據id刪除文檔
    @Test
    public void testDelDoc() throws IOException {
        //刪除文檔id
        String id = "eqP_amQBKsGOdwJ4fHiC";

        //刪除索引請求對象
        DeleteRequest deleteRequest = new DeleteRequest("xc_course","doc",id);
        //響應對象
        DeleteResponse deleteResponse = client.delete(deleteRequest);
        //獲取響應結果
        DocWriteResponse.Result result = deleteResponse.getResult();
       System.out.println(result);

    }

搜索匹配刪除還沒有具體的api,可以採用先搜索出文檔id,根據文檔id刪除。

7搜索管理

7.1 準備環境

7.1.1 創建映射

創建xc_course索引庫。

創建如下映射

post:http://localhost:9200/xc_course/doc/_mapping

參考 “資料”--》搜索測試-初始化數據.txt

{
                "properties": {
                    "description": {
                        "type": "text",
                        "analyzer": "ik_max_word",
                        "search_analyzer": "ik_smart"
                    },
                    "name": {
                        "type": "text",
                        "analyzer": "ik_max_word",
                        "search_analyzer": "ik_smart"
                    },
					"pic":{
						"type":"text",
						"index":false
					},
                    "price": {
                        "type": "float"
                    },
                    "studymodel": {
                        "type": "keyword"
                    },
                    "timestamp": {
                        "type": "date",
                        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
                    }
                }
            }

7.1.2 插入原始數據

向xc_course/doc中插入以下數據:

參考 “資料”--》搜索測試-初始化數據.txt

http://localhost:9200/xc_course/doc/1
{
"name": "Bootstrap開發",
"description": "Bootstrap是由Twitter推出的一個前臺頁面開發css框架,是一個非常流行的開發框架,此框架集成了多種頁面效果。此開發框架包含了大量的CSS、JS程序代碼,可以幫助開發者(尤其是不擅長css頁面開發的程序人員)輕鬆的實現一個css,不受瀏覽器限制的精美界面css效果。",
"studymodel": "201002",
"price":38.6,
"timestamp":"2018-04-25 19:11:35",
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
http://localhost:9200/xc_course/doc/2
{
"name": "java編程基礎",
"description": "java語言是世界第一編程語言,在軟件開發領域使用人數最多。",
"studymodel": "201001",
"price":68.6,
"timestamp":"2018-03-25 19:11:35",
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
http://localhost:9200/xc_course/doc/3
{
"name": "spring開發基礎",
"description": "spring 在java領域非常流行,java程序員都在用。",
"studymodel": "201001",
"price":88.6,
"timestamp":"2018-02-24 19:11:35",
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}

7.1.3 簡單搜索

簡單搜索就是通過url進行查詢,以get方式請求ES。

格式:get ../_search?q=.....

q:搜索字符串。

例子:

?q=name:spring 搜索name中包括spring的文檔。

7.3 DSL搜索

DSL(Domain Specific Language)是ES提出的基於json的搜索方式,在搜索時傳入特定的json格式的數據來完成不同的搜索需求。

DSL比URI搜索方式功能強大,在項目中建議使用DSL方式來完成搜索。

7.3.1 查詢所有文檔

查詢所有索引庫的文檔。

發送:post http://localhost:9200/_search

查詢指定索引庫指定類型下的文檔。(通過使用此方法)

發送:post http://localhost:9200/xc_course/doc/_search

{
  "query": {
      "match_all": {}
  },
   "_source" : ["name","studymodel"]
}

_source:source源過慮設置,指定結果中所包括的字段有哪些。

結果說明:

took:本次操作花費的時間,單位爲毫秒。

timed_out:請求是否超時

_shards:說明本次操作共搜索了哪些分片

hits:搜索命中的記錄

hits.total : 符合條件的文檔總數 hits.hits :匹配度較高的前N個文檔

hits.max_score:文檔匹配得分,這裏爲最高分

_score:每個文檔都有一個匹配度得分,按照降序排列。

_source:顯示了文檔的原始內容。

JavaClient:

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestSearch {

    @Autowired
    RestHighLevelClient client;

    @Autowired
    RestClient restClient;

	//搜索type下的全部記錄
   @Test
    public void testSearchAll() throws IOException, ParseException {
        //搜索請求對象
        SearchRequest searchRequest = new SearchRequest("xc_course");
        //設置類型
        searchRequest.types("doc");
        //搜索源構建對象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //搜索全部
        searchSourceBuilder.query(QueryBuilders.matchAllQuery());
        //source源字段過慮
        searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
        //設置搜索源
        searchRequest.source(searchSourceBuilder);
        //執行搜索
        SearchResponse searchResponse = client.search(searchRequest);
        //搜索匹配結果
        SearchHits hits = searchResponse.getHits();
        //搜索總記錄數
        long totalHits = hits.totalHits;
        //匹配度較高的前N個文檔
        SearchHit[] searchHits = hits.getHits();
        //日期格式化對象
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for(SearchHit hit:searchHits){
            //文檔id
            String id = hit.getId();
            //源文檔內容
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            //獲取源文檔name
            String name = (String) sourceAsMap.get("name");
            String description = (String) sourceAsMap.get("description");
            String studymodel = (String) sourceAsMap.get("studymodel");
            Double price = (Double) sourceAsMap.get("price");
            Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
            System.out.println(name);
            System.out.println(studymodel);
            System.out.println(description);
        }


    }
  ....

7.3.2 分頁查詢

ES支持分頁查詢,傳入兩個參數:from和size。

form:表示起始文檔的下標,從0開始。

size:查詢的文檔數量。

發送:post http://localhost:9200/xc_course/doc/_search

{
"from" : 0, "size" : 1,
"query": {
   "match_all": {}
 },
"_source" : ["name","studymodel"]

}

JavaClient

...
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//設置分頁參數
//當前頁碼
int page = 2;//頁碼
int size = 1;//每頁顯示個數
int from = (page - 1) * size;//起記錄下標
searchSourceBuilder.from(from);
searchSourceBuilder.size(size);
...

7.3.3 Term Query

Term Query爲精確查詢,在搜索時會整體匹配關鍵字,不再將關鍵字分詞。

發送:post http://localhost:9200/xc_course/doc/_search

{
   "query": {
      "term" : {
          "name": "spring"
      }
	},
 	"_source" : ["name","studymodel"]
 }

上邊的搜索會查詢name包括“spring”這個詞的文檔。

JavaClient:

...
//搜索請求對象
SearchRequest searchRequest = new SearchRequest("xc_course");
//設置類型
searchRequest.types("doc");
//搜索源構建對象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//TermQuery
searchSourceBuilder.query(QueryBuilders.termQuery("name","spring"));
//source源字段過慮
searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
//設置搜索源
searchRequest.source(searchSourceBuilder);
//執行搜索
SearchResponse searchResponse = client.search(searchRequest);
...

7.3.4 根據id精確匹配

ES提供根據多個id值匹配的方法:

測試:

post: http://127.0.0.1:9200/xc_course/doc/_search

{
    "query": {
        "ids" : {
            "type" : "doc",
            "values" : ["3", "4", "100"]
        }
    }
}

JavaClient:

通過termsQuery進行查詢,代碼如下:

//搜索請求對象
SearchRequest searchRequest = new SearchRequest("xc_course");
//設置類型
searchRequest.types("doc");
//搜索源構建對象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//主鍵
String[] ids = new String[]{"1","2"};
//TermQuery
searchSourceBuilder.query(QueryBuilders.termsQuery("_id", ids));
//source源字段過慮
searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
//設置搜索源
searchRequest.source(searchSourceBuilder);
//執行搜索
SearchResponse searchResponse = client.search(searchRequest);

7.3.5 match Query

1、基本使用

​ match Query即全文檢索,它的搜索方式是先將搜索字符串分詞,再使用各各詞條從索引中搜索。

match query與Term query區別是match query在搜索前先將搜索關鍵字分詞,再拿各各詞語去索引中搜索。

發送:post http://localhost:9200/xc_course/doc/_search

{
	"query": {
    "match" : {
        "description" : {
            "query" : "spring開發",
            "operator" : "or"
        }
    }
  }
}

query:搜索的關鍵字,對於英文關鍵字如果有多個單詞則中間要用半角逗號分隔,而對於中文關鍵字中間可以用逗號分隔也可以不用。

operator:or 表示 只要有一個詞在文檔中出現則就符合條件,and表示每個詞都在文檔中出現則才符合條件。

上邊的搜索的執行過程是:

1、將“spring開發”分詞,分爲spring、開發兩個詞

2、再使用spring和開發兩個詞去匹配索引中搜索。

3、由於設置了operator爲or,只要有一個詞匹配成功則就返回該文檔。

JavaClient:

//根據關鍵字搜索
@Test
    public void testMatchQuery() throws IOException, ParseException {
        //搜索請求對象
        SearchRequest searchRequest = new SearchRequest("xc_course");
        //設置類型
        searchRequest.types("doc");
        //搜索源構建對象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //MatcherQuery
        searchSourceBuilder.query(QueryBuilders.matchQuery("description","spring開發").operator(Operator.OR));
        //source源字段過慮
        searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
        //設置搜索源
        searchRequest.source(searchSourceBuilder);
        //執行搜索
        SearchResponse searchResponse = client.search(searchRequest);
        //搜索匹配結果
        SearchHits hits = searchResponse.getHits();
        //搜索總記錄數
        long totalHits = hits.totalHits;
        //匹配度較高的前N個文檔
        SearchHit[] searchHits = hits.getHits();
        //日期格式化對象
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for(SearchHit hit:searchHits){
            //文檔id
            String id = hit.getId();
            //源文檔內容
            Map<String, Object> sourceAsMap = hit.getSourceAsMap();
            //獲取源文檔name
            String name = (String) sourceAsMap.get("name");
            String description = (String) sourceAsMap.get("description");
            String studymodel = (String) sourceAsMap.get("studymodel");
            Double price = (Double) sourceAsMap.get("price");
            Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
            System.out.println(name);
            System.out.println(studymodel);
            System.out.println(description);
        }


    }

2、minimum_should_match

上邊使用的operator = or表示只要有一個詞匹配上就得分,如果實現三個詞至少有兩個詞匹配如何實現?

使用minimum_should_match可以指定文檔匹配詞的佔比:

比如搜索語句如下:

{
	"query": {
    "match" : {
        "description" : {
            "query" : "spring開發框架",
             "minimum_should_match": "80%"
        }
    }
  }
}

“spring開發框架”會被分爲三個詞:spring、開發、框架

設置"minimum_should_match": "80%"表示,三個詞在文檔的匹配佔比爲80%,即3*0.8=2.4,向上取整得2,表示至少有兩個詞在文檔中要匹配成功。

對應的RestClient如下:

//MatcherQuery
        searchSourceBuilder.query(QueryBuilders.matchQuery("description","spring開發框架")
                                    .operator(Operator.OR)
                                    .minimumShouldMatch("80%"));
     

7.3.6 multi Query

上邊學習的termQuery和matchQuery一次只能匹配一個Field,本節學習multiQuery,一次可以匹配多個字段。

1、基本使用

單項匹配是在一個field中去匹配,多項匹配是拿關鍵字去多個Field中匹配。

例子:

發送:post http://localhost:9200/xc_course/doc/_search

拿關鍵字 “spring css”去匹配name 和description字段。

{
   "query": {
    "multi_match" : {
            "query" : "spring css",
            "minimum_should_match": "50%",
            "fields": [ "name", "description" ]
     }
    }
}

2、提升boost

匹配多個字段時可以提升字段的boost(權重)來提高得分

例子:

提升boost之前,執行下邊的查詢:

{
   "query": {
    "multi_match" : {
            "query" : "spring css",
            "minimum_should_match": "50%",
            "fields": [ "name", "description" ]
     }
    }
}

通過查詢發現Bootstrap排在前邊。

提升boost,通常關鍵字匹配上name的權重要比匹配上description的權重高,這裏可以對name的權重提升。

{
   "query": {
    "multi_match" : {
            "query" : "spring css",
            "minimum_should_match": "50%",
            "fields": [ "name^10", "description" ]
     }
    }
}

“name^10” 表示權重提升10倍,執行上邊的查詢,發現name中包括spring關鍵字的文檔排在前邊。

JavaClient:

//搜索源構建對象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//MultiMatcherQuery
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("spring css", "name", "description")
        .minimumShouldMatch("50%")
        .field("name", 10);
searchSourceBuilder.query(matchQueryBuilder);

7.3.7 布爾查詢

布爾查詢對應於Lucene的BooleanQuery查詢,實現將多個查詢組合起來。

三個參數:

must:文檔必須匹配must所包括的查詢條件,相當於 “AND” should:文檔應該匹配should所包括的查詢條件其中的一個或多個,相當於 "OR" must_not:文檔不能匹配must_not所包括的該查詢條件,相當於“NOT”

分別使用must、should、must_not測試下邊的查詢:

發送:POST http://localhost:9200/xc_course/doc/_search

{
	"_source" : [ "name", "studymodel", "description"],
	"from" : 0, "size" : 1,
   "query": {
   	 "bool" : {
   		"must":[
   			{
   				"multi_match" : {
            	"query" : "spring css",
            	"minimum_should_match": "50%",
            	"fields": [ "name^10", "description" ]
    			}
   			},
   			{
   				"term":{
   					"studymodel" : "201001"
   				}
   			}
   		]
   	 }
    }
}

must:表示必須,多個查詢條件必須都滿足。(通常使用must)

should:表示或者,多個查詢條件只要有一個滿足即可。

must_not:表示非。

JavaClient:

//BoolQuery,將搜索關鍵字分詞,拿分詞去索引庫搜索
//搜索源構建對象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//MultiMatcherQuery
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("spring css", "name", "description")
        .minimumShouldMatch("50%")
        .field("name", 10);
//TermQuery
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("studymodel", "201001");
//boolQueryBuilder
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//將MultiMatcherQuery和TermQuery組織在一起
boolQueryBuilder.must(matchQueryBuilder);
boolQueryBuilder.must(termQueryBuilder);
searchSourceBuilder.query(boolQueryBuilder);

7.3.8 過慮器

​ 過慮是針對搜索的結果進行過慮,過慮器主要判斷的是文檔是否匹配,不去計算和判斷文檔的匹配度得分,所以過慮器性能比查詢要高,且方便緩存,推薦儘量使用過慮器去實現查詢或者過慮器和查詢共同使用。

過慮器在布爾查詢中使用,下邊是在搜索結果的基礎上進行過慮:

{
	"_source" : [ "name", "studymodel", "description","price"],
   "query": {
   	 "bool" : {
   		"must":[
   			{
   				"multi_match" : {
            	"query" : "spring css",
            	"minimum_should_match": "50%",
            	"fields": [ "name^10", "description" ]
    			}
   			}
   		],
   		"filter": [ 
        	{ "term":  { "studymodel": "201001" }}, 
        	{ "range": { "price": { "gte": 60 ,"lte" : 100}}} 
		]
   	 }
    }
}

range:範圍過慮,保留大於等於60 並且小於等於100的記錄。

term:項匹配過慮,保留studymodel等於"201001"的記錄。

注意:range和term一次只能對一個Field設置範圍過慮。

client:

//搜索源構建對象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//MultiMatcherQuery
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("spring css", "name", "description")
        .minimumShouldMatch("50%")
        .field("name", 10);

//boolQueryBuilder
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//將MultiMatcherQuery和TermQuery組織在一起
boolQueryBuilder.must(matchQueryBuilder);
//添加過慮器
//項過慮
boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel","201001"));
//範圍過慮
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
searchSourceBuilder.query(boolQueryBuilder);

7.3.9 排序

可以在字段上添加一個或多個排序,支持在keyword、date、float等類型上添加,text類型的字段上不允許添加排序。

發送 POST http://localhost:9200/xc_course/doc/_search

過慮0--10元價格範圍的文檔,並且對結果進行排序,先按studymodel降序,再按價格升序

{
	"_source" : [ "name", "studymodel", "description","price"],
   "query": {
   	 "bool" : {
   		"filter": [ 
        	{ "range": { "price": { "gte": 0 ,"lte" : 100}}} 
		]
   	 }
    },
   "sort" : [
   	  {
         "studymodel" : "desc"
      },
   		{
         "price" : "asc"
      }
   	]
}

client:

@Test
public void testSort() throws IOException, ParseException {
    //搜索請求對象
    SearchRequest searchRequest = new SearchRequest("xc_course");
    //設置類型
    searchRequest.types("doc");
    //搜索源構建對象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //source源字段過慮
    searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
    //boolQueryBuilder
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    //添加過慮器
    //範圍過慮
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100));
    searchSourceBuilder.query(boolQueryBuilder);
    //設置搜索源
    searchRequest.source(searchSourceBuilder);
    //設置排序
    searchSourceBuilder.sort("studymodel", SortOrder.DESC);
    searchSourceBuilder.sort("price", SortOrder.ASC);
    //執行搜索
    SearchResponse searchResponse = client.search(searchRequest);
    //搜索匹配結果
    SearchHits hits = searchResponse.getHits();
    //搜索總記錄數
    long totalHits = hits.totalHits;
    //匹配度較高的前N個文檔
    SearchHit[] searchHits = hits.getHits();
    //日期格式化對象
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for(SearchHit hit:searchHits){
        //文檔id
        String id = hit.getId();
        //源文檔內容
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        //獲取源文檔name
        String name = (String) sourceAsMap.get("name");
        String description = (String) sourceAsMap.get("description");
        String studymodel = (String) sourceAsMap.get("studymodel");
        Double price = (Double) sourceAsMap.get("price");
        Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
        System.out.println(name);
        System.out.println(studymodel);
        System.out.println(description);
    }
}

7.3.10 高亮顯示

高亮顯示可以將搜索結果一個或多個字突出顯示,以便向用戶展示匹配關鍵字的位置。

在搜索語句中添加highlight即可實現,如下:

Post: http://127.0.0.1:9200/xc_course/doc/_search

{
	"_source" : [ "name", "studymodel", "timestamp","price"],
   "query": {
   	 "bool" : {
   		"must":[
   			{
   				"multi_match" : {
            	"query" : "開發框架",
            	"minimum_should_match": "50%",
            	"fields": [ "name^10", "description" ]
    			}
   			}
   		],
   		"filter": [ 
        	{ "range": { "price": { "gte": 0 ,"lte" : 100}}} 
		]
   	 }
    },
   "sort" : [
   		{
         "price" : "asc"
      }
   	],
   	"highlight": {
    "pre_tags": ["<tag1>"],
    "post_tags": ["</tag2>"], 
    "fields": {
      "name": {},
      "description":{}
    }
  }
}

client代碼如下:

@Test
public void testHighLight() throws IOException, ParseException {
    //搜索請求對象
    SearchRequest searchRequest = new SearchRequest("xc_course");
    //設置類型
    searchRequest.types("doc");
    //搜索源構建對象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    //source源字段過慮
    searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
    //MultiMatcherQuery
    MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("開發框架", "name", "description")
            .minimumShouldMatch("80%")
            .field("name", 10);
    //boolQueryBuilder
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.must(matchQueryBuilder);
    //添加過慮器
    //範圍過慮
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100));
    searchSourceBuilder.query(boolQueryBuilder);
    //設置搜索源
    searchRequest.source(searchSourceBuilder);
    //設置排序
    searchSourceBuilder.sort("studymodel", SortOrder.DESC);
    searchSourceBuilder.sort("price", SortOrder.ASC);
    //設置高亮
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<tag>");
    highlightBuilder.postTags("</tag>");
    highlightBuilder.fields().add(new HighlightBuilder.Field("name"));
    searchSourceBuilder.highlighter(highlightBuilder);
    //執行搜索
    SearchResponse searchResponse = client.search(searchRequest);
    //搜索匹配結果
    SearchHits hits = searchResponse.getHits();
    //搜索總記錄數
    long totalHits = hits.totalHits;
    //匹配度較高的前N個文檔
    SearchHit[] searchHits = hits.getHits();
    //日期格式化對象
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for(SearchHit hit:searchHits){
        //文檔id
        String id = hit.getId();
        //源文檔內容
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        //獲取源文檔name
        String name = (String) sourceAsMap.get("name");
        //取出高亮字段
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        if(highlightFields!=null){
        	//取出name高亮字段
            HighlightField nameField = highlightFields.get("name");
            if(nameField!=null){
                Text[] fragments = nameField.fragments();
                StringBuffer stringBuffer = new StringBuffer();
                for(Text text:fragments){
                    stringBuffer.append(text);
                }
                name = stringBuffer.toString();
            }

        }


        String description = (String) sourceAsMap.get("description");
        String studymodel = (String) sourceAsMap.get("studymodel");
        Double price = (Double) sourceAsMap.get("price");
        Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
        System.out.println(name);
        System.out.println(studymodel);
        System.out.println(description);
    }
}

8 集羣管理

8.1 集羣結構

ES通常以集羣方式工作,這樣做不僅能夠提高 ES的搜索能力還可以處理大數據搜索的能力,同時也增加了系統的容錯能力及高可用,ES可以實現PB級數據的搜索。

下圖是ES集羣結構的示意圖:

從上圖總結以下概念:

1、結點

ES集羣由多個服務器組成,每個服務器即爲一個Node結點(該服務只部署了一個ES進程)。

2、分片

當我們的文檔量很大時,由於內存和硬盤的限制,同時也爲了提高ES的處理能力、容錯能力及高可用能力,我們將索引分成若干分片,每個分片可以放在不同的服務器,這樣就實現了多個服務器共同對外提供索引及搜索服務。

一個搜索請求過來,會分別從各各分片去查詢,最後將查詢到的數據合併返回給用戶。

3、副本

爲了提高ES的高可用同時也爲了提高搜索的吞吐量,我們將分片複製一份或多份存儲在其它的服務器,這樣即使當前的服務器掛掉了,擁有副本的服務器照常可以提供服務。

4、主結點

一個集羣中會有一個或多個主結點,主結點的作用是集羣管理,比如增加節點,移除節點等,主結點掛掉後ES會重新選一個主結點。

5、結點轉發

每個結點都知道其它結點的信息,我們可以對任意一個結點發起請求,接收請求的結點會轉發給其它結點查詢數據。

8.2 搭建集羣

下邊的例子實現創建一個2結點的集羣,並且索引的分片我們設置2片,每片一個副本。

8.2.1 結點的三個角色

主結點:master節點主要用於集羣的管理及索引 比如新增結點、分片分配、索引的新增和刪除等。 數據結點:data 節點上保存了數據分片,它負責索引和搜索操作。 客戶端結點:client 節點僅作爲請求客戶端存在,client的作用也作爲負載均衡器,client 節點不存數據,只是將請求均衡轉發到其它結點。

通過下邊兩項參數來配置結點的功能:

node.master: #是否允許爲主結點

node.data: #允許存儲數據作爲數據結點

node.ingest: #是否允許成爲協調節點,

四種組合方式:

master=true,data=true:即是主結點又是數據結點

master=false,data=true:僅是數據結點

master=true,data=false:僅是主結點,不存儲數據

master=false,data=false:即不是主結點也不是數據結點,此時可設置ingest爲true表示它是一個客戶端。

8.2.2創建結點 1

解壓elasticsearch-6.2.1.zip 到 F:\devenv\elasticsearch\es-cloud-1\elasticsearch-6.2.1

結點1對外服務的http端口是:9200

集羣管理端口是9300

配置elasticsearch.yml

結點名:xc_node_1

elasticsearch.yml內容如下

cluster.name: xuecheng

node.name: xc_node_1

network.host: 0.0.0.0

http.port: 9200

transport.tcp.port: 9300

node.master: true

node.data: true

discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]

discovery.zen.minimum_master_nodes: 1

node.ingest: true

node.max_local_storage_nodes: 2

path.data: D:\ElasticSearch\elasticsearch-6.2.1-1\data

path.logs: D:\ElasticSearch\elasticsearch-6.2.1-1\logs

http.cors.enabled: true

http.cors.allow-origin: /.*/

啓動結點1

8.2.3創建結點 2

解壓elasticsearch-6.2.1.zip 到 F:\devenv\elasticsearch\es-cloud-2\elasticsearch-6.2.1

結點1對外服務的http端口是:9201

集羣管理端口是9301

結點名:xc_node_2

elasticsearch.yml內容如下

cluster.name: xuecheng

node.name: xc_node_2

network.host: 0.0.0.0

http.port: 9201

transport.tcp.port: 9301

node.master: true

node.data: true

discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]

discovery.zen.minimum_master_nodes: 1

node.ingest: true

node.max_local_storage_nodes: 2

path.data: D:\ElasticSearch\elasticsearch-6.2.1-2\data

path.logs: D:\ElasticSearch\elasticsearch-6.2.1-2\logs

http.cors.enabled: true

http.cors.allow-origin: /.*/

啓動結點2

8.2.4 創建索引庫

1)使用head連上其中一個結點

上圖表示兩個結點已經創建成功。

2)下邊創建索引庫,共2個分片,每個分片一個副本。

創建成功,刷新head:

上圖可以看到共有4個分片,其中兩個分片是副本。

3)每個結點安裝IK分詞器

8.2.5 集羣的健康

通過訪問 GET /_cluster/health 來查看Elasticsearch 的集羣健康情況。

用三種顏色來展示健康狀態: green 、 yellow 或者 red 。

green:所有的主分片和副本分片都正常運行。 yellow:所有的主分片都正常運行,但有些副本分片運行不正常。 red:存在主分片運行不正常。

Get請求:http://localhost:9200/_cluster/health

響應結果:

{
    "cluster_name": "xuecheng",
    "status": "green",
    "timed_out": false,
    "number_of_nodes": 2,
    "number_of_data_nodes": 2,
    "active_primary_shards": 2,
    "active_shards": 4,
    "relocating_shards": 0,
    "initializing_shards": 0,
    "unassigned_shards": 0,
    "delayed_unassigned_shards": 0,
    "number_of_pending_tasks": 0,
    "number_of_in_flight_fetch": 0,
    "task_max_waiting_in_queue_millis": 0,
    "active_shards_percent_as_number": 100
}

8.3 測試

1)創建映射並寫入文檔

連接 其中任意一臺結點,創建映射寫入文檔。

Post http://localhost:9200/xc_course/doc/3

{
"name": "spring開發基礎",
"description": "spring 在java領域非常流行,java軟件開發人員都在用。",
"studymodel": "201001",
"price":66.6
}

響應結果:

{
    "_index": "xc_course",
    "_type": "doc",
    "_id": "3",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 2,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

從上邊的提示可看出,兩個分片都保存成功。

2)搜索

向其它一個結點發起搜索請求,查詢全部數據。

3)關閉一個結點

ES會重新選中一個主結點(前提在配置結點時允許它可以爲主結點)

此時向活的結點發起搜索請求,仍然正常。

4)添加一個結點

添加結點3,端口設置爲:

http端口是:9202

集羣管理端口是9302

結點名:xc_node_3

此結點的配置:

node.master: false node.data: true

啓動結點3,刷新head,下圖顯示ES將分片及副本均勻分在了3個結點(注意環境不同分佈的結果可能不同)

向結點3發起搜索請求:

Get: http://127.0.0.1:9202/xc_course/doc/_search

全部數據可被正常搜索到。

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