Elasticsearch入門及掌握其JavaAPI

我的技術博客:www.zhenganwen.topcss

環境

安裝ES

ES項目結構

解壓elasticsearch-6.2.1.zip,解壓後獲得的目錄爲==ES根目錄==,其中各目錄做用以下:前端

  • bin,存放啓動ES等命令腳本
  • config,存放ES的配置文件,ES啓動時會讀取其中的內容
    • elasticsearch.yml,ES的集羣信息、對外端口、內存鎖定、數據目錄、跨域訪問等屬性的配置
    • jvm.options,ES使用Java寫的,此文件用於設置JVM相關參數,如最大堆、最小堆
    • log4j2.properties,ES使用log4j做爲其日誌框架
  • data,數據存放目錄(索引數據)
  • lib,ES依賴的庫
  • logs,日誌存放目錄
  • modules,ES的各功能模塊
  • plugins,ES的可擴展插件存放目錄,如能夠將ik中文分詞插件放入此目錄,ES啓動時會自動加載

屬性配置

默認的elasticsearch.yml中的全部屬性都被註釋了,咱們須要設置一些必要的屬性值,在文尾添加以下內容(集羣相關的配將在後文詳細說明):java

cluster.name: xuecheng #集羣名稱,默認爲elasticsearch
node.name: xc_node_1   #節點名稱,一個ES實例就是一個節點(一般一臺機器上只部署一個ES實例)
network.host: 0.0.0.0  #IP綁定,0.0.0.0表示全部IP均可訪問到此ES實例
http.port: 9200	       #經過此端口以RESTful的形式訪問ES
transport.tcp.port: 9300 #ES集羣通訊使用的端口
node.master: true	   #此節點是否可以做爲主節點
node.data: true  	   #此節點是否存放數據
discovery.zen.ping.unicast.hosts: ["0.0.0.0:9300", "0.0.0.0:9301"]	#集羣其餘節點的通訊端口,ES啓動時會發現這些節點
discovery.zen.minimum_master_nodes: 1	#主節點數量的最少值,此值的計算公式爲(master_eligible_nodes/2)+1,便可做爲主節點的節點數/2+1
node.ingest: true	   #此節點是否做爲協調節點,當索引庫具備多個分片而且各分片位於不一樣節點上時,若是收到查詢請求的節點發現要查詢的數據在另外一個節點的分片上,那麼做爲協調節點的該節點將會轉發此請求並最終響應結果數據
bootstrap.memory_lock: false	#是否鎖住ES佔用的內存,此項涉及到OS的swap概念,當ES空閒時操做系統可能會把ES佔用的內存數據暫時保存到磁盤上,當ES活動起來時再調入內存,若是要求ES時刻保持迅速響應狀態則可設置爲true,那麼ES的運行內存永遠不會被交換到磁盤以免交換過程帶來的延時
node.max_local_storage_nodes: 2	#本機上的最大存儲節點數,多個ES實例能夠共享一個數據目錄,這一特性有利於咱們在開發環境的一臺機器上測試集羣機制,但在生產環境下建議設置爲1,而且官方也建議一臺集羣僅部署一個ES實例

path.data: D:\software\es\elasticsearch-6.2.1\data	#ES的數據目錄
path.logs: D:\software\es\elasticsearch-6.2.1\logs	#ES的日誌目錄

http.cors.enabled: true			#是否容許跨域訪問,後面經過一個可視化ES管理插件時須要經過js跨域訪問此ES
http.cors.allow-origin: /.*/	#設置全部域都可跨域訪問此ES
複製代碼

JVM參數設置

默認ES啓動須要分配的堆內存爲1G,若是你的機器內存較小則可在jvm.options中調整爲512M:node

-Xms512m
-Xmx512
複製代碼

啓動ES

雙擊/bin/elasticsearch.bat啓動腳本便可啓動ES,關閉該命令行窗口便可關閉ES。git

啓動後訪問:http://localhost:9200,若是獲得以下響應則ES啓動成功:程序員

{
    name: "xc_node_1",
    cluster_name: "xuecheng",
    cluster_uuid: "93K4AFOVSD-kPF2DdHlcow",
    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"
}
複製代碼

elasticsearch-head可視化插件

ES是基於Lucene開發的產品級搜索引擎,封裝了不少內部細節,經過此插件咱們能夠經過Web的方式可視化查看其內部狀態。github

此插件無需放到ES的/plugins目錄下,由於它是經過JS與ES進行交互的。web

git clone git://github.com/mobz/elasticsearch-head.git
cd elasticsearch-head
npm install
npm run start
複製代碼

瀏覽器打開:http://localhost:9100,並鏈接經過ES提供的http端口鏈接ES:spring

image

ES快速入門

首先咱們要理解幾個概念:索引庫(index)、文檔(document)、字段(field),咱們能夠類比關係型數據庫來理解:shell

ES MySQL
索引庫index 數據庫database
type 表table
文檔document 行row
字段field 列column

可是自ES6.x開始,type的概念就慢慢被弱化了,官方將在ES9正式剔除它。所以咱們能夠將索引庫類比爲一張表。一個索引庫用來存儲一系列結構相似的數據。雖然能夠經過多個type製造出一個索引庫「多張表」的效果,但官方不建議這麼作,由於這會下降索引和搜索性能,要麼你就新建另一個索引庫。類比MySQL來看就是,一個庫就只放一張表。

名詞索引 & 動詞索引

名詞索引指的是索引庫,一個磁盤上的文件。

一個索引庫就是一張倒排索引表,將數據存入ES的過程就是先將數據分詞而後添加到倒排索引表的過程。

image

以添加「中華人民共和國」、「中華上下五千年」到索引庫爲例,倒排索引表的邏輯結構以下:

term doc_id
中華 一、2
人民 1
共和國 1
上下 2
2
千年 2
doc_id doc
1 中華人民共和國
2 中華上下五千年

這種將數據分詞並創建各分詞到文檔之間的關聯關係的過程稱爲==索引==(動詞)

Postman

Postman是一款HTTP客戶端工具,可否方便地發送各類形式的RESTful請求。

下文將以Postman來測試ES的RESTful API,請求的根URL爲:http://localhost:9200

索引庫管理

建立索引庫

建立一個名爲「xc_course」的用於存放學成在線(教育平臺)的課程數據的索引庫:

  • PUT /xc_course
{
	"settings":{
		"number_of_shards":1,		//索引庫分片數量
		"number_of_replicas":0		//每一個分片的副本數,關於分片、集羣等在後文詳細介紹
	}
}
複製代碼

image

建立成功了嗎?咱們能夠經過elasticsearch-head來查看,刷新localhost:9100:

image

刪除索引庫

DELET /xc_course

查看索引信息

GET /xc_course

映射管理

建立映射

映射能夠類比MySQL的表結構定義,若有哪些字段,字段是什麼類型。

建立映射的請求格式爲:POST /index_name/type_name/_mapping。

不是說type已經弱化了嗎?爲何這裏還要指定type的名稱?由於在ES9才正式剔除type的概念,在此以前須要一個過渡期,所以咱們能夠指定一個無心義的type名,如「doc」:

POST /xc_course/doc/_mapping

{
	"properties":{
		"name":{
			"type":"text"
		},
		"description":{
			"type":"text"
		},
		"price":{
			"type":"double"
		}
	}
}
複製代碼

查看映射(類比查看錶結構)

GET /xc_course/doc/_mapping

{
    "xc_course": {
        "mappings": {
            "doc": {
                "properties": {
                    "description": {
                        "type": "text"
                    },
                    "name": {
                        "type": "text"
                    },
                    "price": {
                        "type": "double"
                    }
                }
            }
        }
    }
}
複製代碼

也能夠經過head插件查看:

image

文檔管理

添加文檔

PUT /index/type/id

若是不指定id,那麼ES會爲咱們自動生成:

PUT /xc_course/doc

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

響應以下:

{
    "_index": "xc_course",
    "_type": "doc",
    "_id": "Hib0QmoB7xBOMrejqjF3",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 1,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}
複製代碼

根據id查詢文檔

GET /index/type/id

因而咱們拿到咱們剛添加數據生成的id來查詢:

GET /xc_course/doc/Hib0QmoB7xBOMrejqjF3

{
    "_index": "xc_course",
    "_type": "doc",
    "_id": "Hib0QmoB7xBOMrejqjF3",
    "_version": 1,
    "found": true,
    "_source": {
        "name": "Bootstrap開發框架",
        "description": "Bootstrap是由Twitter推出的一個前臺頁面開發框架,在行業之中使用較爲普遍。此開發框架包含了大量的CSS、JS程序代碼,能夠幫助開發者(尤爲是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。",
        "price": 99.9
    }
}
複製代碼

查詢所有文檔

GET /index/type/_search

{
    "took": 64,				//這次查詢花費時間
    "timed_out": false,		
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {			   
        "total": 1,
        "max_score": 1,
        "hits": [			//查詢匹配的文檔集合
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "Hib0QmoB7xBOMrejqjF3",
                "_score": 1,
                "_source": {
                    "name": "Bootstrap開發框架",
                    "description": "Bootstrap是由Twitter推出的一個前臺頁面開發框架,在行業之中使用較爲普遍。此開發框架包含了大量的CSS、JS程序代碼,能夠幫助開發者(尤爲是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。",
                    "price": 99.9
                }
            }
        ]
    }
}
複製代碼

IK中文分詞器

ES默認狀況下是不支持中文分詞的,也就是說對於添加的中文數據,ES將會把每一個字當作一個term(詞項),這不利於中文檢索。

測試ES默認狀況下對中文分詞的結果:

POST /_analyze

你會發現ES的固定API都會帶上_前綴,如_mapping_search_analyze

{
	"text":"中華人民共和國"
}
複製代碼

分詞結果以下:

{
    "tokens": [
        {
            "token": "中",
            "start_offset": 0,
            "end_offset": 1,
            "type": "<IDEOGRAPHIC>",
            "position": 0
        },
        {
            "token": "華",
            "start_offset": 1,
            "end_offset": 2,
            "type": "<IDEOGRAPHIC>",
            "position": 1
        },
        {
            "token": "人",
            "start_offset": 2,
            "end_offset": 3,
            "type": "<IDEOGRAPHIC>",
            "position": 2
        },
        {
            "token": "民",
            "start_offset": 3,
            "end_offset": 4,
            "type": "<IDEOGRAPHIC>",
            "position": 3
        },
        {
            "token": "共",
            "start_offset": 4,
            "end_offset": 5,
            "type": "<IDEOGRAPHIC>",
            "position": 4
        },
        {
            "token": "和",
            "start_offset": 5,
            "end_offset": 6,
            "type": "<IDEOGRAPHIC>",
            "position": 5
        },
        {
            "token": "國",
            "start_offset": 6,
            "end_offset": 7,
            "type": "<IDEOGRAPHIC>",
            "position": 6
        }
    ]
}
複製代碼

下載ik-6.4.0並解壓到ES/plugins/目錄下,並將解壓後的目錄更名爲==ik==,==重啓ES==,該插件即會被自動加載。

重啓ES後再測試分詞效果:

POST http://localhost:9200/_analyze

{
	"text":"中華人民共和國",
	"analyzer":"ik_max_word"	//設置分詞器爲ik分詞器,不然仍是會採用默認分詞器,可選ik_max_word和ik_smart
}
複製代碼

ik_max_word分詞策略是儘量的分出多的term,即細粒度分詞:

{
    "tokens": [
        {
            "token": "中華人民共和國",
            "start_offset": 0,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "中華人民",
            "start_offset": 0,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 1
        },
        {
            "token": "中華",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "華人",
            "start_offset": 1,
            "end_offset": 3,
            "type": "CN_WORD",
            "position": 3
        },
        {
            "token": "人民共和國",
            "start_offset": 2,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 4
        },
        {
            "token": "人民",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 5
        },
        {
            "token": "共和國",
            "start_offset": 4,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 6
        },
        {
            "token": "共和",
            "start_offset": 4,
            "end_offset": 6,
            "type": "CN_WORD",
            "position": 7
        },
        {
            "token": "國",
            "start_offset": 6,
            "end_offset": 7,
            "type": "CN_CHAR",
            "position": 8
        }
    ]
}
複製代碼

而ik_smart則是出粒度分詞(設置"analyzer" : "ik_smart"):

{
    "tokens": [
        {
            "token": "中華人民共和國",
            "start_offset": 0,
            "end_offset": 7,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}
複製代碼

自定義詞庫

ik分詞器僅提供了經常使用中文短語的詞庫,而對於實時性的熱門網絡短語則沒法識別,所以有時爲了增長分詞準確性,咱們須要本身擴展詞庫。

首先咱們測試ik對網絡詞彙「藍瘦香菇」的分詞效果:

PUT /_analyze

{
    "text":"藍瘦香菇",
    "analyzer":"ik_smart"
}
複製代碼

分詞以下:

{
    "tokens": [
        {
            "token": "藍",
            "start_offset": 0,
            "end_offset": 1,
            "type": "CN_CHAR",
            "position": 0
        },
        {
            "token": "瘦",
            "start_offset": 1,
            "end_offset": 2,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "香菇",
            "start_offset": 2,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 2
        }
    ]
}
複製代碼

咱們在ES的/plugins/ik/config目錄下增長自定義的詞庫文件my.dic並添加一行「藍瘦香菇」(詞典文件的格式是每個詞項佔一行),並在ik的配置文件/plugins/ik/config/IKAnalyzer.cfg.xml中引入該自定義詞典:

<!--用戶能夠在這裏配置本身的擴展字典 -->
<entry key="ext_dict">my.dic</entry>
複製代碼

==重啓ES==,ik分詞器將會把咱們新增的詞項做爲分詞標準:

{
    "tokens": [
        {
            "token": "藍瘦香菇",
            "start_offset": 0,
            "end_offset": 4,
            "type": "CN_WORD",
            "position": 0
        }
    ]
}
複製代碼

映射

新增字段

PUT /xc_course/doc/_mapping

{
    "properties":{
        "create_time":{
            "type":"date"
        }
    }
}
複製代碼

GET /xc_course/doc/_mapping

{
    "xc_course": {
        "mappings": {
            "doc": {
                "properties": {
                    "create_time": {
                        "type": "date"
                    },
                    "description": {
                        "type": "text"
                    },
                    "name": {
                        "type": "text"
                    },
                    "price": {
                        "type": "double"
                    }
                }
            }
        }
    }
}
複製代碼

已有的映射能夠新增字段但不能夠更改已有字段的定義!

PUT /xc_course/doc/_mapping

{
    "properties":{
        "price":{
            "type":"integer"
        }
    }
}
複製代碼

報錯:已定義的price不能從double類型更改成integer類型:

{
    "error": {
        "root_cause": [
            {
                "type": "illegal_argument_exception",
                "reason": "mapper [price] cannot be changed from type [double] to [integer]"
            }
        ],
        "type": "illegal_argument_exception",
        "reason": "mapper [price] cannot be changed from type [double] to [integer]"
    },
    "status": 400
}
複製代碼

若是必定要更改某字段的定義(包括類型、分詞器、是否索引等),那麼只有刪除此索引庫從新創建索引並定義好各字段,再遷入數據。所以在索引庫建立時要考慮好映射的定義,由於僅可擴展字段但不可從新定義字段。

經常使用的映射類型——type

ES6.2的核心數據類型以下:

image

keyword

此類型的字段不會被分詞,該字段內容被表示爲就是一個短語不可分割。如各大商標和品牌名可以使用此類型。而且在該字段查詢內容時是精確匹配,如在type爲keyword的brand字段搜索「華爲」不會搜出字段值爲「華爲榮耀」的文檔。

date

type爲date的字段還能夠額外指定一個format,如

{
    "properties":{
        "create_time":{	
            "type":"date",
            "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"	
        }
    }
}
複製代碼

新增文檔的create_time字段值能夠是日期+時間或僅日期

數值類型

image

一、儘可能選擇範圍小的類型,提升搜索效率 二、對於浮點數儘可能用比例因子,好比一個價格字段,單位爲元,咱們將比例因子設置爲100這在ES中會按==分==存 儲,映射以下:

"price": {
    "type": "scaled_float",       
    "scaling_factor": 100
}
複製代碼

因爲比例因子爲100,若是咱們輸入的價格是23.45則ES中會將23.45乘以100存儲在ES中。若是輸入的價格是23.456,ES會將23.456乘以100再取一個接近原始值的數,得出2346。

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

是否創建索引——index

index默認爲true,即須要分詞並根據分詞所得詞項創建倒排索引(詞項到文檔的關聯關係)。有些字段的數據是無實際意義的,如課程圖片的url僅做展現圖片之用,不須要分詞創建索引,那麼能夠設置爲false:

PUT /xc_course/doc/_mapping

{
    "properties":{
        "pic":{
            "type":"text"
            "index":"false"
        }
    }
}
複製代碼

索引分詞器 & 搜索分詞器

索引分詞器——analyzer

將數據添加到索引庫時使用的分詞器,建議使用ik_max_word,好比「中華人民共和國」,若是使用ik_smart,那麼整個「中華人民共和國」將被做爲一個term(此項)存入倒排索引表,那麼在搜索「共和國」時就搜不到此數據(詞項與詞項之間是精確匹配的)。

搜索分詞器——search_analyzer

搜索分詞器則是用於將用戶的檢索輸入分詞的分詞器。

建議使用ik_smart,好比搜索「中華人民共和國」,不該該出現「喜馬拉雅共和國」的內容。

是否額外存儲——store

是否在source以外存儲,每一個文檔索引後會在 ES中保存一份原始文檔,存放在_source中,通常狀況下不須要設置 store爲true,由於在_source中已經有一份原始文檔了。

綜合實戰

建立一個課程集合的映射:

  1. 首先刪除已創建映射的索引

    DELET /xc_course

  2. 新增索引

    PUT /xc_course

  3. 建立映射

    PUT /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"
            },
            "price": {
                "type": "scaled_float",
                "scaling_factor": 100
            },
            "studypattern": {
                "type": "keyword"
            },
            "pic": {
                "type": "text",
                "index": false
            },
            "timestamp": {
                "type": "date",
                "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
            }
        }
    }
    複製代碼
  4. 添加文檔

    POST /xc_course/doc

    {
        "name": "Java核心技術",
        "description": "深刻淺出講解Java核心技術以及相關原理",
        "price": 99.9,
        "studypattern": "20101",
        "pic": "http://xxx.xxx.xxx/skllsdfsdflsdfk.img",
        "timestamp": "2019-4-1 13:16:00"
    }
    複製代碼
  5. 檢索Java

    GET http://localhost:9200/xc_course/doc/_search?q=name:java

  6. 檢索學習模式

    GET http://localhost:9200/xc_course/doc/_search?q=studypattern:20101

索引管理和Java客戶端

今後章節開始咱們將對ES的每一個RESTful API實現配套的Java代碼。畢竟雖然前端能夠經過HTTP訪問ES,可是ES的管理和定製化業務仍是須要一個後端做爲樞紐。

ES提供的Java客戶端——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 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>
複製代碼

Spring整合ES

依賴

<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-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
</dependency>	
複製代碼

配置文件

application.yml

server:
 port: ${port:40100}
spring:
 application:
 name: xc-service-search
xuecheng:	#自定義屬性項
 elasticsearch:
 host-list: ${eshostlist:127.0.0.1:9200} #多個節點中間用逗號分隔
複製代碼

啓動類

@SpringBootApplication
public class SearchApplication {
    public static void main(String[] args){
        SpringApplication.run(SearchApplication.class, args);
    }
}
複製代碼

ES配置類

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;

/** * ElasticsearchConfig class * * @author : zaw * @date : 2019/4/22 */
@Configuration
public class ElasticsearchConfig {

    @Value("${xuecheng.elasticsearch.host-list}")
    private String hostList;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        return new RestHighLevelClient(RestClient.builder(getHttpHostList(hostList)));
    }

    private HttpHost[] getHttpHostList(String hostList) {
        String[] hosts = hostList.split(",");
        HttpHost[] httpHostArr = new HttpHost[hosts.length];
        for (int i = 0; i < hosts.length; i++) {
            String[] items = hosts[i].split(":");
            httpHostArr[i] = new HttpHost(items[0], Integer.parseInt(items[1]), "http");
        }
        return httpHostArr;
    }

    // rest low level client
    @Bean
    public RestClient restClient() {
        return RestClient.builder(getHttpHostList(hostList)).build();
    }
}
複製代碼

測試類

package com.xuecheng.search;

import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.client.IndicesClient;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

/** * TestES class * * @author : zaw * @date : 2019/4/22 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestESRestClient {

    @Autowired
    RestHighLevelClient restHighLevelClient;    //ES鏈接對象

    @Autowired
    RestClient restClient;
}

複製代碼

ES客戶端API

首先咱們將以前建立的索引庫刪除:

DELETE /xc_course

而後回顧一下建立索引庫的RESTful形式:

PUT /xc_course

{
	"settings":{
		"index":{
			"number_of_shards":1,
			"number_of_replicas":0
		}
	}
}
複製代碼

建立索引庫

@Test
public void testCreateIndex() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("xc_course");
    /** * { * "settings":{ * "index":{ * "number_of_shards":1, * "number_of_replicas":0 * } * } * } */
    request.settings(Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0));
    IndicesClient indicesClient = restHighLevelClient.indices();    //經過ES鏈接對象獲取索引庫管理對象
    CreateIndexResponse response = indicesClient.create(request);
    System.out.println(response.isAcknowledged());  //操做是否成功
}
複製代碼

對比RESTful形式,經過CreateIndexRequest方式發起這次請求,第3行經過構造函數指明瞭要建立的索引庫名(對應URI /xc_course),第14行構造了請求體(你會發現settings方法和JSON請求格式很類似)。

操做索引庫須要使用IndicesClient對象。

刪除索引庫

@Test
public void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("xc_course");
    IndicesClient indicesClient = restHighLevelClient.indices();
    DeleteIndexResponse response = indicesClient.delete(request);
    System.out.println(response.isAcknowledged());
}
複製代碼

建立索引庫時指定映射

@Test
public void testCreateIndexWithMapping() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("xc_course");
    request.settings(Settings.builder().put("number_of_shards", 1).put("number_of_replicas", 0));
    request.mapping("doc", "{\n" +
                    " \"properties\": {\n" +
                    " \"name\": {\n" +
                    " \"type\": \"text\",\n" +
                    " \"analyzer\": \"ik_max_word\",\n" +
                    " \"search_analyzer\": \"ik_smart\"\n" +
                    " },\n" +
                    " \"price\": {\n" +
                    " \"type\": \"scaled_float\",\n" +
                    " \"scaling_factor\": 100\n" +
                    " },\n" +
                    " \"timestamp\": {\n" +
                    " \"type\": \"date\",\n" +
                    " \"format\": \"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd\"\n" +
                    " }\n" +
                    " }\n" +
                    "}", XContentType.JSON);
    IndicesClient indicesClient = restHighLevelClient.indices();
    CreateIndexResponse response = indicesClient.create(request);
    System.out.println(response.isAcknowledged());
}
複製代碼

添加文檔

添加文檔的過程就是「索引」(動詞)。須要使用IndexRequest對象進行索引操做。

public static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Test
public void testAddDocument() throws IOException {
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("name", "Java核心技術");
    jsonMap.put("price", 66.6);
    jsonMap.put("timestamp", FORMAT.format(new Date(System.currentTimeMillis())));
    IndexRequest request = new IndexRequest("xc_course", "doc");
    request.source(jsonMap);
    IndexResponse response = restHighLevelClient.index(request);
    System.out.println(response);
}
複製代碼

響應結果包含了ES爲咱們生成的文檔id,這裏我測試獲得的id爲fHh6RWoBduPBueXKl_tz

根據id查詢文檔

@Test
public void testFindById() throws IOException {
    GetRequest request = new GetRequest("xc_course", "doc", "fHh6RWoBduPBueXKl_tz");
    GetResponse response = restHighLevelClient.get(request);
    System.out.println(response);
}
複製代碼

根據id更新文檔

ES更新文檔有兩種方式:全量替換和局部更新

全量替換:ES首先會根據id查詢文檔並刪除而後將該id做爲新文檔的id插入。

局部更新:只會更新相應字段

全量替換:

POST /index/type/id

局部更新:

POST /index/type/_update

Java客戶端提供的是局部更新,即僅對提交的字段進行更新而其餘字段值不變

@Test
public void testUpdateDoc() throws IOException {
    UpdateRequest request = new UpdateRequest("xc_course", "doc", "fHh6RWoBduPBueXKl_tz");
    Map<String, Object> docMap = new HashMap<>();
    docMap.put("name", "Spring核心技術");
    docMap.put("price", 99.8);
    docMap.put("timestamp", FORMAT.format(new Date(System.currentTimeMillis())));
    request.doc(docMap);
    UpdateResponse response = restHighLevelClient.update(request);
    System.out.println(response);
    testFindById();
}
複製代碼

根據id刪除文檔

@Test
public void testDeleteDoc() throws IOException {
    DeleteRequest request = new DeleteRequest("xc_course", "doc", "fHh6RWoBduPBueXKl_tz");
    DeleteResponse response = restHighLevelClient.delete(request);
    System.out.println(response);
}
複製代碼

搜索管理

準備環境

爲了有數據可搜,咱們從新建立映射並添加一些測試數據

建立映射

DELETE /xc_course

PUT /xc_course

{
    "settings":{
        "number_of_shards":1,
        "number_of_replicas":0
    }
}
複製代碼

PUT /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"	//授課模式,值爲數據字典代號
		},
        "pic": {
            "type": "text",
            "index": false
        },
        "price": {
            "type": "float"
        },
        "timestamp": {
            "type": "date",
            "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd"
        }
    }
}
複製代碼

添加測試數據

PUT /xc_course/doc/1

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

PUT /xc_course/doc/2

{
    "name": "java編程基礎",
    "description": "java語言是世界第一編程語言,在軟件開發領域使用人數最多。",
    "studymodel": "201001",
    "price": 68.6,
    "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
    "timestamp": "2018-03-25 19:11:35"
}
複製代碼

PUT /xc_course/doc/3

{
    "name": "spring開發基礎",
    "description": "spring 在java領域很是流行,java程序員都在用。",
    "studymodel": "201001",
    "price": 88.6,
    "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
    "timestamp": "2018-02-24 19:11:35"
}
複製代碼

簡單搜索

  • 搜索指定索引庫中的全部文檔

    GET /xc_course/_search

  • 搜索指定type中的全部文檔

    GET /xc_course/doc/_search

DSL搜索

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

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

DSL搜索方式是使用POST提交,URI爲以_search結尾(在某index或某type範圍內搜索),而在JSON請求體中定義搜索條件。

查詢全部文檔——matchAllQuery

POST /xc_course/doc/_search

{
	"query":{
		"match_all":{}
	},
	"_source":["name","studymodel"]
}
複製代碼

query用來定義搜索條件,_source用來指定返回的結果集中須要包含哪些字段。這在文檔自己數據量較大但咱們只想獲取其中特定幾個字段數據時有用(既可過濾掉沒必要要字段,又可提升傳輸效率)。

結果說明:

  • took,本次操做花費的時間,單位毫秒
  • time_out,請求是否超時(ES不可用或網絡故障時會超時)
  • _shard,本次操做共搜索了哪些分片
  • hits,命中的結果
  • hits.total,符合條件的文檔數
  • hits.hits,命中的文檔集
  • hits.max_score,hits.hits中各文檔得分的最高分,文檔得分即查詢相關度
  • _source,文檔源數據
{
    "took": 57,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "studymodel": "201002",
                    "name": "Bootstrap開發"
                }
            },
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "2",
                "_score": 1,
                "_source": {
                    "studymodel": "201001",
                    "name": "java編程基礎"
                }
            },
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "3",
                "_score": 1,
                "_source": {
                    "studymodel": "201001",
                    "name": "spring開發基礎"
                }
            }
        ]
    }
}
複製代碼

Java代碼實現:

@Test
public void testMatchAll() throws IOException {
    // POST /xc_course/doc
    SearchRequest request = new SearchRequest("xc_course");    //DSL搜索請求對象
    request.types("doc");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();    //DSL請求體構造對象
    /** * { * "from":2,"size":1, * "query":{ * "match_all":{ * * }* }, * "_source":["name","studymodel"] * } */
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    //參數1:要返回哪些字段 參數2:不要返回哪些字段 二者一般指定其一
    searchSourceBuilder.fetchSource(new String[]{"name", "studymodel"}, null);
    //將請求體設置到請求對象中
    request.source(searchSourceBuilder);
    //發起DSL請求
    SearchResponse response = restHighLevelClient.search(request);
    System.out.println(response);
}
複製代碼

DSL核心API

  • new SearchRequest(index),指定要搜索的索引庫
  • searchRequest.type(type),指定要搜索的type
  • SearchSourceBuilder,構建DSL請求體
  • searchSourceBuilder.query(queryBuilder),構造請求體中「query」:{}部分的內容
  • QueryBuilders,靜態工廠類,方便構造queryBuilder,如searchSourceBuilder.query(QueryBuilders.matchAllQuery())就至關於構造了「query」:{ "match_all":{} }
  • searchRequest.source(),將構造好的請求體設置到請求對象中

分頁查詢

PUT http://localhost:9200/xc_course/doc/_search

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

其中from的含義是結果集偏移,而size則是從偏移位置開始以後的size條結果。

{
    "took": 80,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
            {
                "_index": "xc_course",
                "_type": "doc",
                "_id": "1",
                "_score": 1,
                "_source": {
                    "studymodel": "201002",
                    "name": "Bootstrap開發"
                }
            }
        ]
    }
}
複製代碼

這裏雖然hits.total爲3,可是隻返回了第一條記錄。所以咱們在作分頁功能時須要用到一個公式:from = (page-1)*size

Java代碼實現

@Test
public void testPaginating() throws IOException {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    int page = 1, size = 1;
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.from((page - 1) * size);
    searchSourceBuilder.size(size);
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    searchSourceBuilder.fetchSource(new String[]{"name", "studymodel"}, null);

    request.source(searchSourceBuilder);
    SearchResponse response = restHighLevelClient.search(request);
    System.out.println(response);
}
複製代碼

提取結果集中的文檔

SearchResponse response = restHighLevelClient.search(request);
SearchHits hits = response.getHits();				//hits
if (hits != null) {
    SearchHit[] results = hits.getHits();			//hits.hits
    for (SearchHit result : results) {
        System.out.println(result.getSourceAsMap()); //hits.hits._source
    }
}
複製代碼

詞項匹配——termQuery

詞項匹配是==精確匹配==,只有當倒排索引表中存在咱們指定的詞項時纔會返回該詞項關聯的文檔集。

如搜索課程名包含java詞項的文檔

{
	"from":0,"size":1,
	"query":{
		"term":{ "name":"java" }
	},
	"_source":["name","studymodel"]
}
複製代碼

結果以下:

"hits": {
    "total": 1,
    "max_score": 0.9331132,
    "hits": [
        {
            "_index": "xc_course",
            "_type": "doc",
            "_id": "2",
            "_score": 0.9331132,
            "_source": {
                "studymodel": "201001",
                "name": "java編程基礎"
            }
        }
    ]
}
複製代碼

但若是你指定"term"{ "name":"java編程" }就搜索不到了:

"hits": {
    "total": 0,
    "max_score": null,
    "hits": []
}
複製代碼

由於「java編程基礎」在索引時會被分爲「java」、「編程」、「基礎」三個詞項添加到倒排索引表中,所以沒有一個叫「java編程」的詞項和這次查詢匹配。

term查詢是精確匹配,term.name不會被search_analyzer分詞,而是會做爲一個總體和倒排索引表中的詞項進行匹配。

根據id精確匹配——termsQuery

查詢id爲1和3的文檔

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

{
	"query":{
		"ids":{
			"values":["1","3"]
		}
	}
}
複製代碼

Java實現

@Test
public void testQueryByIds(){
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    List<String> ids = Arrays.asList(new String[]{"1", "3"});
    sourceBuilder.query(QueryBuilders.termsQuery("_id", ids));

    printResult(request, sourceBuilder);
}

private void printResult(SearchRequest request,SearchSourceBuilder sourceBuilder) {
    request.source(sourceBuilder);
    SearchResponse response = null;
    try {
        response = restHighLevelClient.search(request);
    } catch (IOException e) {
        e.printStackTrace();
    }
    SearchHits hits = response.getHits();
    if (hits != null) {
        SearchHit[] results = hits.getHits();
        for (SearchHit result : results) {
            System.out.println(result.getSourceAsMap());
        }
    }
}
複製代碼

==大坑==

根據id精確匹配也是term查詢的一種,可是調用的API是termsQuery("_id", ids),注意是termsQuery而不是termQuery

全文檢索—— matchQuery

輸入的關鍵詞會被search_analyzer指定的分詞器分詞,而後根據所得詞項到倒排索引表中查找文檔集合,每一個詞項關聯的文檔集合都會被查出來,如查「bootstrap基礎」會查出「java編程基礎」:

POST

{
	"query":{
		"match":{
			"name":"bootstrap基礎"
		}
	}
}
複製代碼

由於「bootstrap基礎」會被分爲「bootstrap」和「基礎」兩個詞項,而詞項「基礎」關聯文檔「java編程基礎」。

@Test
public void testMatchQuery() {

    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("name", "bootstrap基礎"));

    printResult(request, sourceBuilder);
}
複製代碼

operator

上述查詢等同於:

{
    "query": {
        "match": {
            "name": {
                "query": "bootstrap基礎",
                "operator": "or"
            }
        }
    }
}
複製代碼

即對檢索關鍵詞分詞後每一個詞項的查詢結果取並集。

operator可取值orand,分別對應取並集和取交集。

以下查詢就只有一結果(課程名既包含「java」又包含「基礎」的只有「java編程基礎」):

{
    "query": {
        "match": {
            "name": {
                "query": "java基礎",
                "operator": "and"
            }
        }
    }
}
複製代碼

Java代碼

@Test
public void testMatchQuery2() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("name", "java基礎").operator(Operator.AND));

    printResult(request, sourceBuilder);
}
複製代碼

minimum_should_match

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

使用minimum_should_match能夠指定文檔匹配詞的佔比,好比搜索語句以下:

{
    "query": {
        "match": {
            "name": {
                "query": "spring開發框架",
                "minimum_should_match":"80%"
            }
        }
    }
}
複製代碼

「spring開發框架」會被分爲三個詞:spring、開發、框架。

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

@Test
public void testMatchQuery3() {
    SearchRequest request = new SearchRequest("xc_course");
request.types("doc");

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("name", "spring開發指南").minimumShouldMatch("70%")); //3*0.7 -> 2

printResult(request, sourceBuilder);
}
複製代碼

多域檢索——multiMatchQuery

上邊學習的termQuerymatchQuery一次只能匹配一個Field,本節學習multiQuery,一次能夠匹配多個字段(即擴大了檢索範圍,以前一直都是在name字段中檢索)。

如檢索課程名或課程描述中包含「spring」或「css」的文檔:

{
    "query": {
        "multi_match": {
            "query": "spring css",
            "minimum_should_match": "50%",
            "fields": [
                "name",
                "description"
            ]
        }
    },
    "_source":["name","description"]
}
複製代碼

Java:

@Test
public void testMultiMatchQuery() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.
                        multiMatchQuery("spring css","name","description").
                        minimumShouldMatch("50%")); 

    printResult(request, sourceBuilder);
}
複製代碼

boost權重

觀察上述查出的文檔得分:

"hits": [
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "3",
        "_score": 1.3339276,
        "_source": {
            "name": "spring開發基礎",
            "description": "spring 在java領域很是流行,java程序員都在用。"
        }
    },
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "1",
        "_score": 0.69607234,
        "_source": {
            "name": "Bootstrap開發",
            "description": "Bootstrap是由Twitter推出的一個前臺頁面開發框架,是一個很是流行的開發框架,此框架集成了多種頁面效果。此開發框架包含了大量的CSS、JS程序代碼,能夠幫助開發者(尤爲是不擅長頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。"
        }
    }
]
複製代碼

你會發現文檔3spring詞項在文檔出現的次數佔文檔詞項總數的比例較高所以得分(_score)較高。那咱們猜測,是否是咱們在文檔1的課程描述中多添加幾個css可否提高其_score呢?

因而咱們更新一下文檔1:

@Test
public void testUpdateDoc() throws IOException {
    UpdateRequest request = new UpdateRequest("xc_course", "doc", "1");
    Map<String, Object> docMap = new HashMap<>();
    docMap.put("description", "Bootstrap是由Twitter推出的一個css前臺頁面開發框架,是一個很是流行的css開發框架,此框架集成了多種css頁面效果。此開發框架包含了大量的CSS、JS程序代碼,能夠幫助css開發者(尤爲是不擅長css頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。");
    request.doc(docMap);
    UpdateResponse response = restHighLevelClient.update(request);
    System.out.println(response);
    testFindById();
}
複製代碼

再次查詢發現文檔1的得分果真變高了:

"hits": [
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "1",
        "_score": 1.575484,
        "_source": {
            "name": "Bootstrap開發",
            "description": "Bootstrap是由Twitter推出的一個css前臺頁面開發框架,是一個很是流行的css開發框架,此框架集成了多種css頁面效果。此開發框架包含了大量的CSS、JS程序代碼,能夠幫助css開發者(尤爲是不擅長css頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。"
        }
    },
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "3",
        "_score": 1.346281,
        "_source": {
            "name": "spring開發基礎",
            "description": "spring 在java領域很是流行,java程序員都在用。"
        }
    }
]
複製代碼

那咱們有這樣一個業務需求:課程出現spring或css確定是與spring或css相關度更大的課程,而課程描述出現則不必定。所以咱們想提升課程出現關鍵詞項的得分權重,咱們能夠這麼辦(在name字段後追加一個^符號並指定權重,默認爲1):

{
    "query": {
        "multi_match": {
            "query": "spring css",
            "minimum_should_match": "50%",
            "fields": [
                "name^10",
                "description"
            ]
        }
    },
    "_source":["name","description"]
}
複製代碼

Java:

@Test
public void testMultiMatchQuery2() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("spring css", "name", "description").minimumShouldMatch("50%");
    multiMatchQueryBuilder.field("name", 10);
    sourceBuilder.query(multiMatchQueryBuilder);

    printResult(request, sourceBuilder);
}
複製代碼

布爾查詢——boolQuery

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

三個參數

  • must:文檔必須匹配must所包括的查詢條件,至關於 「AND」
  • should:文檔應該匹配should所包括的查詢條件其中的一個或多個,至關於 "OR"
  • must_not:文檔不能匹配must_not所包括的該查詢條件,至關於「NOT」

如查詢課程名包含「spring」==且==課程名或課程描述跟「開發框架」有關的:

{
    "query": {
        "bool":{
            "must":[
                {
                    "term":{
                        "name":"spring"
                    }
                },
                {
                    "multi_match":{
                        "query":"開發框架",
                        "fields":["name","description"]
                    }
                }
            ]
        }
    },
    "_source":["name"]
}
複製代碼

Java

@Test
public void testBoolQuery() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();	//query
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();		//query.bool

    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "spring");
    MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("開發框架", "name", "description");

    boolQueryBuilder.must(termQueryBuilder);		//query.bool.must
    boolQueryBuilder.must(multiMatchQueryBuilder);	
    sourceBuilder.query(boolQueryBuilder);

    printResult(request, sourceBuilder);
}
複製代碼

必須知足的條件放到must中(boolQueryBuilder.must(條件)),必須排斥的條件放到must_not中,只需知足其一的條件放到should中。

查詢課程名必須包含「開發」但不包含「java」的,且包含「spring」或「boostrap」的課程:

{
    "query": {
       "bool":{
       		"must":[
				{
					"term":{
						"name":"開發"
					}
				}
       		],
       		"must_not":[
				{
					"term":{
						"name":"java"
					}
				}
       		],
       		"should":[
				{
					"term":{
						"name":"bootstrap"
					}
				},
				{
					"term":{
						"name":"spring"
					}
				}
       		]
       }
    },
    "_source":["name"]
}
複製代碼

固然實際項目不會這麼設置條件,這裏只是爲了演示效果,這裏爲了演示方便用的都是termQuery,事實可用前面任意一種Query

Java

@Test
public void testBoolQuery2() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

    boolQueryBuilder.must(QueryBuilders.termQuery("name","開發"));
    boolQueryBuilder.mustNot(QueryBuilders.termQuery("name", "java"));
    boolQueryBuilder.should(QueryBuilders.termQuery("name","spring"));
    boolQueryBuilder.should(QueryBuilders.termQuery("name","bootstrap"));

    sourceBuilder.query(boolQueryBuilder);

    printResult(request, sourceBuilder);
}
複製代碼

過濾器——filter

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

過濾器僅能在布爾查詢中使用。

全文檢索「spring框架」,並過濾掉學習模式代號不是「201001」和課程價格不在10~100之間的

{
    "query": {
        "bool": {
            "must": [
                {
                    "multi_match": {
                        "query": "spring框架",
                        "fields": [
                            "name",
                            "description"
                        ]
                    }
                }
            ],
            "filter": [
                {
                    "term": {
                        "studymodel": "201001"
                    }
                },
                {
                    "range": {
                        "price": {
                            "gte": "10",
                            "lte": "100"
                        }
                    }
                }
            ]
        }
    }
}
複製代碼

Java

@Test
public void testBoolQuery3() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

    boolQueryBuilder.must(QueryBuilders.multiMatchQuery("spring框架", "name", "description"));
    boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel", "201001"));
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(10).lte(100));

    sourceBuilder.query(boolQueryBuilder);

    printResult(request, sourceBuilder);
}
複製代碼

排序

查詢課程價格在10~100之間的,並按照價格升序排列,當價格相同時再按照時間戳降序排列

{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "price": {
                            "gte": "10",
                            "lte": "100"
                        }
                    }
                }
            ]
        }
    },
    "sort": [
        {
            "price": "asc"
        },
        {
            "timestamp": "desc"
        }
    ],
    "_source": [
        "name",
        "price",
        "timestamp"
    ]
}
複製代碼

Java

@Test
public void testSort() {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");

    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(10).lte(100));

    sourceBuilder.sort("price", SortOrder.ASC);
    sourceBuilder.sort("timestamp", SortOrder.DESC);

    sourceBuilder.query(boolQueryBuilder);
    printResult(request, sourceBuilder);
}
複製代碼

高亮

{
    "query": {
        "bool": {
            "filter": [
                {
                    "multi_match": {
                        "query": "bootstrap",
                        "fields": [
                            "name",
                            "description"
                        ]
                    }
                }
            ]
        }
    },
    "highlight":{
        "pre_tags":["<tag>"],
        "post_tags":["</tag>"],
        "fields":{
            "name":{},
            "description":{}
        }
    }
}
複製代碼

結果:

"hits": [
    {
        "_index": "xc_course",
        "_type": "doc",
        "_id": "1",
        "_score": 0,
        "_source": {
            "name": "Bootstrap開發",
            "description": "Bootstrap是由Twitter推出的一個css前臺頁面開發框架,是一個很是流行的css開發框架,此框架集成了多種css頁面效果。此開發框架包含了大量的CSS、JS程序代碼,能夠幫助css開發者(尤爲是不擅長css頁面開發的程序人員)輕鬆的實現一個不受瀏覽器限制的精美界面效果。",
            "studymodel": "201002",
            "price": 38.6,
            "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
            "timestamp": "2018-04-25 19:11:35"
        },
        "highlight": {
            "name": [
                "<tag>Bootstrap</tag>開發"
            ],
            "description": [
                "<tag>Bootstrap</tag>是由Twitter推出的一個css前臺頁面開發框架,是一個很是流行的css開發框架,此框架集成了多種css頁面效果。"
            ]
        }
    }
]
複製代碼

hits結果集中的每一個結果出了給出源文檔_source以外,還給出了相應的高亮結果highlight

Java

@Test
public void testHighlight() throws IOException {
    SearchRequest request = new SearchRequest("xc_course");
    request.types("doc");
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
    boolQueryBuilder.filter(QueryBuilders.multiMatchQuery("bootstrap","name","description"));

    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<tag>");
    highlightBuilder.postTags("</tag>");
    highlightBuilder.field("name").field("description");

    sourceBuilder.query(boolQueryBuilder);
    sourceBuilder.highlighter(highlightBuilder);
    request.source(sourceBuilder);

    SearchResponse response = restHighLevelClient.search(request);
    SearchHits hits = response.getHits();       //hits
    if (hits != null) {
        SearchHit[] results = hits.getHits();   //hits.hits
        if (results != null) {
            for (SearchHit result : results) {
                Map<String, Object> source = result.getSourceAsMap();   //_source
                String name = (String) source.get("name");
                Map<String, HighlightField> highlightFields = result.getHighlightFields();  //highlight
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    Text[] fragments = highlightField.getFragments();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Text text : fragments) {
                        stringBuilder.append(text.string());
                    }
                    name = stringBuilder.toString();
                }
                System.out.println(name);

                String description = (String) source.get("description");
                HighlightField highlightField2 = highlightFields.get("description");
                if (highlightField2 != null) {
                    Text[] fragments = highlightField2.getFragments();
                    StringBuilder stringBuilder = new StringBuilder();
                    for (Text text : fragments) {
                        stringBuilder.append(text.string());
                    }
                    description = stringBuilder.toString();
                }
                System.out.println(description);
            }
        }
    }
}
複製代碼

比較難理解的API是HighlightFieldshighlightField.getFragments(),咱們須要對比響應JSO的結構來類比理解。

image

咱們能夠經過highlightFields.get()來獲取highlight.namehighlight.description對應的highlightField,可是爲何hightField.getFragment返回的是一個Text[]而不是Text呢。咱們猜想ES將文檔按照句子分紅了多個段,僅對出現關鍵詞項的段進行高亮並返回,因而咱們檢索css測試一下果真如此:

image

所以你須要注意返回的highlight可能並不包含全部原字段內容

image

集羣管理

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

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

image

集羣相關概念

節點

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

分片

當咱們的文檔量很大時,因爲內存和硬盤的限制,同時也爲了提升ES的處理能力、容錯能力及高可用能力,咱們將索引分紅若干分片(能夠類比MySQL中的分區來看,一個表分紅多個文件),每一個分片能夠放在不一樣的服務器,這樣就實現了多個服務器共同對外提供索引及搜索服務。

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

副本

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

主節點

一個集羣中會有一個或多個主節點,主節點的做用是集羣管理,好比增長節點,移除節點等,主節點掛掉後ES會從新選一個主節點。

節點轉發

每一個節點都知道其它節點的信息,咱們能夠對任意一個v發起請求,接收請求的節點會轉發給其它節點查詢數據。

節點的三個角色

主節點

master節點主要用於集羣的管理及索引 好比新增節點、分片分配、索引的新增和刪除等。

數據節點

data 節點上保存了數據分片,它負責索引和搜索操做。

客戶端節點

client 節點僅做爲請求客戶端存在,client的做用也做爲負載均衡器,client 節點不存數據,只是將請求均衡轉發到其它節點。

配置

可在/config/elasticsearch.yml中配置節點的功能:

  • node.master: #是否容許爲主節點
  • node.data: #容許存儲數據做爲數據節點
  • node.ingest: #是否容許成爲協調節點(數據不在當前ES實例上時轉發請求)

四種組合方式:

  • master=true,data=true:便是主節點又是數據節點
  • master=false,data=true:僅是數據節點
  • master=true,data=false:僅是主節點,不存儲數據
  • master=false,data=false:即不是主節點也不是數據節點,此時可設置ingest爲true表示它是一個客戶端。

搭建集羣

下咱們來實現建立一個2節點的集羣,而且索引的分片咱們設置2片,每片一個副本。

解壓elasticsearch-6.2.1.zip兩份爲es-1es-2

配置文件elasticsearch.yml

節點1:

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
bootstrap.memory_lock: false
node.max_local_storage_nodes: 2

path.data: D:\software\es\cluster\es-1\data
path.logs: D:\software\es\cluster\es-1\logs

http.cors.enabled: true
http.cors.allow-origin: /.*/
複製代碼

節點2:

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
bootstrap.memory_lock: false
node.max_local_storage_nodes: 2

path.data: D:\software\es\cluster\es-2\data
path.logs: D:\software\es\cluster\es-2\logs

http.cors.enabled: true
http.cors.allow-origin: /.*/
複製代碼

測試分片

建立索引,索引分兩片,每片有一個副本:

PUT http://localhost:9200/xc_course

{
    "settings": {
        "number_of_shards": 2,
        "number_of_replicas": 1
    }
}
複製代碼

經過head插件查看索引狀態:

image

測試主從複製

寫入數據

POST http://localhost:9200/xc_course/doc

{
	"name":"java編程基礎"
}
複製代碼

兩個結點均有數據:

image

image

集羣的健康

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

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

  • green:全部的主分片和副本分片都正常運行。
  • yellow:全部的主分片都正常運行,但有些副本分片運行不正常。
  • red:存在主分片運行不正常。
相關文章
相關標籤/搜索