ElasticSearch研究

有夢想的老王發表於2020-10-27

前言

​ 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

全部資料可被正常搜尋到。

相關文章