NOI Online 2022 提高組
`ElasticSearch
1. 什麼是ElasticSearch
ElasticSearch 簡稱 ES ,是基於Apache Lucene構建的開源搜尋引擎,是當前流行的企業級搜尋引擎(分散式搜尋引擎)。Lucene本身就可以被認為迄今為止效能最好的一款開源搜尋引擎工具包,但是lucene的API相對複雜,需要深厚的搜尋理論。很難整合到實際的應用中去。同時ES是採用java語言編寫,提供了簡單易用的RestFul API,開發者可以使用其簡單的RestFul API,開發相關的搜尋功能,從而避免lucene的複雜性。 搜尋檢索 資料庫like
2. 什麼是RestFul
REST : 表現層狀態轉化(Representational State Transfer),如果一個架構符合REST原則,就稱它為 RESTful 架構風格。
**資源(Resources): 所謂"資源",就是網路上的一個實體,或者說是網路上的一個具體資訊
表現層(Representation) :我們把"資源"具體呈現出來的形式,叫做它的"表現層"。
狀態轉化(State Transfer):如果客戶端想要操作伺服器,必須通過某種手段,讓伺服器端發生"狀態轉 化"(State Transfer)。而這種轉化是建立在表現層之上的,所以就是"表現層狀態轉化"。
REST原則就是指一個URL代表一個唯一資源,並且通過HTTP協議裡面四個動詞:GET、POST、PUT、DELETE對應四種伺服器端的基本操作: GET用來獲取資源,POST用來更新資源(也可以用於新增資源),PUT用來新增資源(也可以用於更新資源),DELETE用來刪除資源。
3. 什麼是全文檢索
全文檢索是計算機程式通過掃描文章中的每一個詞,對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置。當用戶查詢時根據建立的索引查詢,類似於通過字典的檢索字表查字的過程。
全文檢索(Full-Text Retrieval(檢索))以文字作為檢索物件,找出含有指定詞彙的文字。全面、準確和快速是衡量全文檢索系統的關鍵指標。
關於全文檢索,我們要知道:
** 1. 只處理文字。**
2. 不處理語義。
3. 搜尋時英文不區分大小寫。
4. 結果列表有相關度排序。
4. ES的誕生
多年前,一個叫做Shay Banon 的剛結婚不久的失業開發者,由於妻子要去倫敦學習廚師,他便跟著也去了。在他找工作的過程中,為了給妻子構建一個食譜的搜尋引擎,他開始構建一個早期版本的Lucene。
直接基於Lucene工作會比較困難,所以Shay開始抽象Lucene程式碼以便Java程式設計師可以在應用中新增搜尋功能。他釋出了他的第一個開源專案,叫做“Compass”。
後來Shay找到一份工作,這份工作處在高效能和記憶體資料網格的分散式環境中,因此高效能的、實時的、分散式的搜尋引擎也是理所當然需要的。然後他決定重寫Compass庫使其成為一個獨立的服務叫做Elasticsearch。
第一個公開版本出現在2010年2月,在那之後Elasticsearch已經成為Github上最受歡迎的專案之一,程式碼貢獻者超過300人。一家主營Elasticsearch的公司就此成立,他們一邊提供商業支援一邊開發新功能,不過Elasticsearch將永遠開源且對所有人可用。
Shay的妻子依舊等待著她的食譜搜尋……
5. ES的應用場景 json格式資料 restful
Es主要以輕量級JSON作為資料儲存格式,這點與MongoDB有點類似,但它在讀寫效能上優於 MongoDB 。同時也支援地理位置查詢 ,還方便地理位置和文字混合查詢 。 以及在統計、日誌類資料儲存和分析、視覺化這方面是引領者。
國外:
Wikipedia(維基百科)使用ES提供全文搜尋並高亮關鍵字、StackOverflow(IT問答網站)結合全文搜尋與地理位置查詢、Github使用Elasticsearch檢索1300億行的程式碼。
國內:
百度(在雲分析、網盟、預測、文庫、錢包、風控等業務上都應用了ES,單叢集每天匯入30TB+資料, 總共每天60TB+)、新浪 、阿里巴巴、騰訊等公司均有對ES的使用。
使用比較廣泛的平臺ELK(ElasticSearch 全文檢索伺服器 核心, Logstash, Kibana)。
6. ES的安裝
# 0. 安裝前準備
centos7 +
java 8 +
elastic 7.6.0+
# 1. 在官方網站下載ES
wget http://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.0.tar.gz
# 2. 安裝JDK(必須JDK1.8+)
rpm -ivh jdk-8u181-linux-x64.rpm
/*注意:預設安裝位置 /usr/java/jdk1.8.0_171-amd64*/
# 3. 配置環境變數
vim /etc/profile
在檔案末尾加入:
export JAVA_HOME=/usr/java/jdk1.8.0_171-amd64
export PATH=$PATH:$JAVA_HOME/bin
# 4. 過載系統配置
source /etc/profile
# 5.建立普通使用者(es不能作為root使用者啟動)
a.在linux系統中建立新的組
groupadd es
b.建立新的使用者es並將es使用者放入es組中
useradd es -g es
c.修改es使用者密碼
passwd es
# 6.上傳es到普通使用者的家目錄,並安裝elasticsearch
tar -zxvf elasticsearch-7.6.0.tar.gz
# 7. elasticsearche的目錄結構
bin 可執行的二進位制檔案的目錄
config 配置檔案的目錄
lib 執行時依賴的庫
logs modules 執行時日誌檔案
plugins es中提供的外掛
# 8. 執行es服務
在bin目錄中執行 ./elasticsearch
# 9. 測試ES是否啟動成功
在命令終端中執行: curl http://localhost:9200 出現以下資訊:
{
"name" : "xQK1cwT",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "t7IYk7LKQ0mXcyyrdFWpLg",
"version" : {
"number" : "7.6.0",
"build_hash" : "ccec39f",
"build_date" : "2018-04-12T20:37:28.497551Z",
"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"
}
# 11. 開啟ES遠端訪問
vim elasticsearch.yml 將原來network修改為以下配置:
network.host: 0.0.0.0
# 12. 啟動時錯誤解決方案
a.重新啟動es出現如下錯誤
**ERROR: bootstrap checks failed[1]: max file descriptors [4096] for elasticsearch process is too low,
increase to at least [65536]**
解決方案:
# 切換到root使用者修改
vim /etc/security/limits.conf
# 在最後面追加下面內容
* soft nofile 65536
* hard nofile 65536
* soft nproc 4096
* hard nproc 4096
# 退出重新登入檢測配置是否生效:
ulimit -Hn
ulimit -Sn
ulimit -Hu
ulimit -Su
b.重新啟動出現如下錯誤
**ERROR: max number of threads [3802] for user [chenyn] is too low,increase to at least [4096]**
解決方案:
#進入limits.d目錄下修改配置檔案。
vim /etc/security/limits.d/20-nproc.conf
# 修改為 啟動ES使用者名稱 soft nproc 4096
c.重新啟動出現如下錯誤
**ERROR: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]**
解決方案:
vim /etc/sysctl.conf
vm.max_map_count=655360
#執行以下命令生效:
sysctl -p
# 13. 關閉網路防火牆
systemctl stop firewalld 關閉本次防火牆服務
systemctl disable firewalld 關閉開啟自啟動防火牆服務
# 14. 外部瀏覽器訪問即可
http://es的主機名:9200 出現如下資訊說明安裝成功:
{
"name" : "xQK1cwT",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "t7IYk7LKQ0mXcyyrdFWpLg",
"version" : {
"number" : "7.6.0",
"build_hash" : "ccec39f",
"build_date" : "2018-04-12T20:37:28.497551Z",
"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"
}
7. ES中基本概念
7.1 接近實時(Near Real Time 簡稱NRT)
Elasticsearch是一個接近實時的搜尋平臺。這意味著,從索引一個文件直到這個文件能夠被搜尋到有一個輕微的延遲(通常是1秒內)
7.2 索引(index)
一個索引就是一個擁有幾分相似特徵的文件的集合
。比如說,你可以有一個客戶資料的索引,另一個產品目錄的索引,還有一個訂單資料的索引。一個索引由一個名字來標識(必須全部是小寫字母的)
,並且當我們要對這個索引中的文件進行索引、搜尋、更新和刪除的時候,都要使用到這個名字
。索引類似於關係型資料庫中Database 的概念
。在一個叢集中,如果你想,可以定義任意多的索引。
7.3 型別(type)
一個型別是你的索引的一個邏輯上的分類/分割槽,其語義完全由你來定
。在一個索引中,你可以定義一種或多種型別。通常,會為具有一組共同欄位的文件定義一個型別。比如說,我們假設你運營一個部落格平臺並且將你所有的數 據儲存到一個索引中。在這個索引中,你可以為使用者資料定義一個型別,為部落格資料定義另一個型別,當然,也可 以為評論資料定義另一個型別。型別類似於關係型資料庫中Table的概念
。NOTE: 在5.x版本以前可以在一個索引中定義多個型別,6.x之後版本也可以使用,但是不推薦,7.0.0以後將將不建議使用,在8.x版本中徹底移除一個索引中建立多個型別
棄用該概念的原因:
我們雖然可以通俗的去理解Index比作 SQL 的 Database,Type比作SQL的Table。但這並不準確,因為如果在SQL中,Table 之前相互獨立,同名的欄位在兩個表中毫無關係。
但是在ES中,同一個Index 下不同的 Type 如果有同名的欄位,他們會被 Luecen當作同一個欄位 ,並且他們的定義必須相同。所以我覺得Index現在更像一個表,
而Type欄位並沒有多少意義。目前Type已經被棄用,在7.0開始,一個索引只能建一個Type為_doc
7.4 對映(Mapping)
Mapping是ES中的一個很重要的內容,
它類似於傳統關係型資料中table的schema,用於定義一個索引(index)中的型別(type)的資料的結構
。 在ES中,我們可以手動建立type(相當於table)和mapping(相關與schema),也可以採用預設建立方式。在預設配置下,ES可以根據插入的資料自動地建立type及其mapping。 mapping中主要包括欄位名、欄位資料型別和欄位索引型別
7.5 文件(document)
一個文件是一個可被索引的基礎資訊單元,類似於表中的一條記錄
。比如,你可以擁有某一個員工的文件,也可以擁有某個商品的一個文件。文件以採用了輕量級的資料交換格式JSON(Javascript Object Notation)來表示。
_index
文件所屬索引名稱。
_type
文件所屬型別名。
_id
Doc的主鍵。在寫入的時候,可以指定該Doc的ID值,如果不指定,則系統自動生成一個唯一的UUID值。
_version
文件的版本資訊。Elasticsearch通過使用version來保證對文件的變更能以正確的順序執行,避免亂序造成的資料丟失。
_seq_no
嚴格遞增的順序號,每個文件一個,Shard級別嚴格遞增,保證後寫入的Doc的_seq_no
大於先寫入的Doc的_seq_no。
primary_term
primary_term也和_seq_no
一樣是一個整數,每當Primary Shard發生重新分配時,比如重啟,Primary選舉等,_primary_term會遞增1
found
查詢的ID正確那麼ture, 如果 Id 不正確,就查不到資料,found欄位就是false。
_source
文件的原始JSON資料。
8. Kibana的安裝
Kibana是一個針對Elasticsearch的開源分析及視覺化平臺,使用Kibana可以查詢、檢視並與儲存在ES索引的資料進行互動操作,使用Kibana能執行高階的資料分析,並能以圖表、表格和地圖的形式檢視資料
1. 下載Kibana
https://www.elastic.co/downloads/kibana
2. 安裝下載的kibana
rpm -ivh kibana-7.6.0-x86_64.rpm
3. 查詢kibana的安裝位置
find / -name kibana
4. 編輯kibana配置檔案
[root@localhost /]# vim /etc/kibana/kibana.yml
5. 修改如下配置
server.host: "10.102.115.3" #ES伺服器主機名
elasticsearch.url: "http://10.102.115.3:9200" #ES伺服器地址
6. 啟動kibana
systemctl start kibana
systemctl stop kibana
systemctl status kibana
7. 訪問kibana的web介面
http://10.102.115.3:5601/ #kibana預設埠為5601 使用主機:埠直接訪問即可
9. Kibana的基本操作
9.1 索引(Index)的基本操作
PUT /dangdang/ 建立索引
DELETE /dangdang 刪除索引
DELETE /* 刪除所有索引
GET /_cat/indices?v 檢視索引資訊
9.2 型別(type)的基本操作
建立型別
1.建立/dangdang索引並建立(product)型別
PUT /dangdang
{
"mappings": {
"product": {
"properties": {
"title": { "type": "text" },
"name": { "type": "text" },
"age": { "type": "integer" },
"created": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
}
}
}
注意: 這種方式建立型別要求索引不能存在
Mapping Type: : text , keyword , date ,integer, long , double , boolean or ip
檢視型別
GET /dangdang/_mapping/product # 語法:GET /索引名/_mapping/型別名
9.3 文件(document)的基本操作
新增文件
POST /ems/_doc/1 #/索引/型別/id
{
"name":"趙小六",
"age":23,
"bir":"2012-12-12",
"content":"這是一個好一點的員工"
}
查詢文件
GET /ems/_doc/1
返回結果:
{
"_index": "ems",
"_type": "_doc",
"_id": "1",
"_version": 1,
"_seq_no": 12,
"_primary_term": 1,
"found": true,
"_source": {
"name": "趙小六",
"age": 23,
"bir": "2012-12-12",
"content": "這是一個好一點的員工"
}
}
刪除文件
DELETE /ems/_doc/1
{
"_index": "ems",
"_type": "_doc",
"_id": "1",
"_version": 2,
"result": "deleted", #刪除成功
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 1,
"_primary_term": 1
}
更新文件
1.第一種方式 更新原有的資料
POST /dangdang/_doc/1/_update
{
"doc":{
"name":"xiaohei"
}
}
2.第二種方式 新增新的資料
POST /ems/_doc/1/_update
{
"doc":{
"name":"xiaohei",
"age":11,
"dpet":"你好部門"
}
}
3.第三種方式 在原來資料基礎上更新
POST /ems/_doc/1/_update
{
"script": "ctx._source.age += 5"
}
ES的使用語法風格為:
<REST Verb> /<Index>/<Type>/<ID>
REST操作 /索引/型別/文件id
批量操作
1. 批量索引兩個文件
PUT /dangdang/_doc/_bulk
{"index":{"_id":"1"}}
{"name": "John Doe","age":23,"bir":"2012-12-12"}
{"index":{"_id":"2"}}
{"name": "Jane Doe","age":23,"bir":"2012-12-12"}
2. 更新文件同時刪除文件
POST /dangdang/_doc/_bulk
{"update":{"_id":"1"}}
{"doc":{"name":"lisi"}}
{"delete":{"_id":2}}
{"index":{}}
{"name":"xxx","age":23}
注意:批量時不會因為一個失敗而全部失敗,而是繼續執行後續操作,批量在返回時按照執行的狀態開始返回
10. ES中高階檢索
10.1 檢索方式
ES官方提供了兩中檢索方式:一種是通過 URL 引數進行搜尋,另一種是通過 DSL(Domain Specified Language) 進行搜尋。官方更推薦使用第二種方式第二種方式是基於傳遞JSON作為請求體(request body)格式與ES進行互動,這種方式更強大,更簡潔。
10.2 測試資料
1.刪除索引
DELETE /ems
2.建立索引並指定型別
PUT /ems
{
"mappings":{
"_doc":{
"properties":{
"name":{
"type":"keyword"
},
"age":{
"type":"integer"
},
"bir":{
"type":"date"
},
"content":{
"type":"text"
},
"address":{
"type":"keyword"
}
}
}
}
}
3.插入測試資料
PUT /ems/_doc/_bulk
{"index":{}}
{"name":"小黑","age":23,"bir":"2012-12-12","content":"為開發團隊選擇一款優秀的MVC框架是件難事兒,在眾多可行的方案中決擇需要很高的經驗和水平","address":"北京"}
{"index":{}}
{"name":"王小黑","age":24,"bir":"2012-12-12","content":"Spring 框架是一個分層架構,由 7 個定義良好的模組組成。Spring 模組構建在核心容器之上,核心容器定義了建立、配置和管理 bean 的方式","address":"上海"}
{"index":{}}
{"name":"張小五","age":8,"bir":"2012-12-12","content":"Spring Cloud 作為Java 語言的微服務框架,它依賴於Spring Boot,有快速開發、持續交付和容易部署等特點。Spring Cloud 的元件非常多,涉及微服務的方方面面,井在開源社群Spring 和Netflix 、Pivotal 兩大公司的推動下越來越完善","address":"無錫"}
{"index":{}}
{"name":"win7","age":9,"bir":"2012-12-12","content":"Spring的目標是致力於全方位的簡化Java開發。 這勢必引出更多的解釋, Spring是如何簡化Java開發的?","address":"南京"}
{"index":{}}
{"name":"梅超風","age":43,"bir":"2012-12-12","content":"Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API","address":"杭州"}
{"index":{}}
{"name":"張無忌","age":59,"bir":"2012-12-12","content":"ElasticSearch是一個基於Lucene的搜尋伺服器。它提供了一個分散式多使用者能力的全文搜尋引擎,基於RESTful web介面","address":"北京"}
10.2 URL檢索
GET /ems/_doc/_search?q=*&sort=age:asc
_search 搜尋的API
q=* 匹配所有文件
sort 以結果中的指定欄位排序
10.3 DSL檢索
GET /ems/_doc/_search
{
"query": {"match_all": {}},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
10.4 DSL高階檢索(Query)
0. 查詢所有(match_all)
match_all關鍵字: 返回索引中的全部文件
GET /ems/_doc/_search
{
"query": { "match_all": {} }
}
1. 查詢結果中返回指定條數(size)
size 關鍵字: 指定查詢結果中返回指定條數。 預設返回值10條
GET /ems/_doc/_search
{
"query": { "match_all": {} },
"size": 1
}
2. 分頁查詢(from)
from 關鍵字: 用來指定起始返回位置,和size關鍵字連用可實現分頁效果
GET /ems/_doc/_search
{
"query": {"match_all": {}},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"size": 2,
"from": 1
}
3. 查詢結果中返回指定欄位(_source)
_source 關鍵字: 是一個數組,在陣列中用來指定展示那些欄位
GET /ems/_doc/_search
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}
4. 關鍵詞查詢(term)
term 關鍵字: 用來使用關鍵詞查詢
GET /ems/_doc/_search
{
"query": {
"term": {
"address": {
"value": "北京"
}
}
}
}
NOTE1: 通過使用term查詢得知ES中預設使用分詞器為標準分詞器(StandardAnalyzer),標準分詞器對於英文單詞分詞,對於中文單字不分詞。
NOTE2: 通過使用term查詢得知,在ES的Mapping Type 中 keyword , date ,integer, long , double , boolean or ip 這些型別不分詞,只有text型別分詞。
5. 範圍查詢(range)
range 關鍵字: 用來指定查詢指定範圍內的文件
GET /ems/_doc/_search
{
"query": {
"range": {
"age": {
"gte": 8,
"lte": 30
}
}
}
}
6. 字首查詢(prefix)
prefix 關鍵字: 用來檢索含有指定字首的關鍵詞的相關文件
GET /ems/_doc/_search
{
"query": {
"prefix": {
"content": {
"value": "redis"
}
}
}
}
7. 萬用字元查詢(wildcard)
wildcard 關鍵字: 萬用字元查詢 ? 用來匹配一個任意字元 * 用來匹配多個任意字元
GET /ems/_doc/_search
{
"query": {
"wildcard": {
"content": {
"value": "re*"
}
}
}
}
8. 多id查詢(ids)
ids 關鍵字 : 值為陣列型別,用來根據一組id獲取多個對應的文件
GET /ems/_doc/_search
{
"query": {
"ids": {
"values": ["lg5HwWkBxH7z6xax7W3_","lQ5HwWkBxH7z6xax7W3_"]
}
}
}
9. 模糊查詢(fuzzy)
fuzzy 關鍵字: 用來模糊查詢含有指定關鍵字的文件 注意:允許出現的錯誤必須在0-2之間
GET /ems/_doc/_search
{
"query": {
"fuzzy": {
"content":"spoong"
}
}
}
# 注意: 最大編輯距離為 0 1 2
如果關鍵詞為2個長度 0..2 must match exactly 必須完全匹配
如果關鍵詞長度3..5之間 one edit allowed 允許一個失敗
如果關鍵詞長度>5 two edits allowed 最多允許兩個錯誤
10. 布林查詢(bool)
bool 關鍵字: 用來組合多個條件實現複雜查詢 boolb表示式查詢
must: 相當於&& 同時成立
should: 相當於|| 成立一個就行
must_not: 相當於! 不能滿足任何一個
GET /ems/_doc/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"age": {
"gte": 0,
"lte": 30
}
}
}
],
"must_not": [
{"wildcard": {
"content": {
"value": "redi?"
}
}}
]
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
11. 高亮查詢(highlight)
highlight 關鍵字: 可以讓符合條件的文件中的關鍵詞高亮
GET /ems/_doc/_search
{
"query": {
"term": {
"content": {
"value": "redis"
}
}
},
"highlight": {
"fields": {
"*": {}
}
}
}
自定義高亮html標籤: 可以在highlight中使用
pre_tags
和post_tags
GET /ems/_doc/_search
{
"query":{
"term":{
"content":"框架"
}
},
"highlight": {
"pre_tags": ["<span style='color:red'>"],
"post_tags": ["</span>"],
"fields": {
"*":{}
}
}
}
多欄位高亮 使用
require_field_match
開啟多個欄位高亮
GET /ems/_doc/_search
{
"query":{
"term":{
"content":"框架"
}
},
"highlight": {
"pre_tags": ["<span style='color:red'>"],
"post_tags": ["</span>"],
"require_field_match":false,
"fields": {
"*":{}
}
}
}
12. 多欄位查詢(multi_match)
注意:使用這種方式進行查詢時,為了更好獲取搜尋結果,在查詢過程中先將查詢條件根據當前的分詞器分詞之後進行查詢
GET /ems/_doc/_search
{
"query": {
"multi_match": {
"query": "中國",
"fields": ["name","content"] #這裡寫要檢索的指定欄位
}
}
}
13. 多欄位分詞查詢(query_String)
注意:使用這種方式進行查詢時,為了更好獲取搜尋結果,在查詢過程中先將查詢條件根據當前的分詞器分詞之後進行查詢
GET /dangdang/book/_search
{
"query": {
"query_string": {
"query": "中國聲音",
"analyzer": "ik_max_word",
"fields": ["name","content"]
}
}
}
11. IK分詞器
NOTE: 預設ES中採用標準分詞器進行分詞,這種方式並不適用於中文網站,因此需要修改ES對中文友好分詞,從而達到更加的搜尋的效果。
11.1 線上安裝IK
線上安裝IK (v5.5.1版本後開始支援線上安裝 )
1. 在es安裝目錄中執行如下命令
[es@linux elasticsearch-7.6.0]$ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
-> Downloading https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
[=================================================] 100%
-> Installed analysis-ik
[es@linux elasticsearch-7.6.0]$ ls plugins/
analysis-ik
[es@linux elasticsearch-7.6.0]$ cd plugins/analysis-ik/
[es@linux analysis-ik]$ ls
commons-codec-1.9.jar elasticsearch-analysis-ik-7.6.0.jar httpcore-4.4.4.jar
commons-logging-1.2.jar httpclient-4.5.2.jar plugin-descriptor.properties
2. 重啟es生效
NOTE: 要求版本嚴格與當前使用版本一致,如需使用其他版本替換
7.6.0
為使用的版本號
11.2 本地安裝IK
可以將對應的IK分詞器下載到本地,然後再安裝
1. 下載對應版本
[es@linux ~]$ wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
2. 解壓
[es@linux ~]$ unzip elasticsearch-analysis-ik-7.6.0.zip #先使用yum install -y unzip
3. 移動到es安裝目錄的plugins目錄中
[es@linux ~]$ ls elasticsearch-7.6.0/plugins/
[es@linux ~]$ mv elasticsearch elasticsearch-7.6.0/plugins/
[es@linux ~]$ ls elasticsearch-7.6.0/plugins/
elasticsearch
[es@linux ~]$ ls elasticsearch-7.6.0/plugins/elasticsearch/
commons-codec-1.9.jar config httpclient-4.5.2.jar plugin-descriptor.properties
commons-logging-1.2.jar elasticsearch-analysis-ik-7.6.0.jar httpcore-4.4.4.jar
4. 重啟es生效
11.3 測試IK分詞器
NOTE: IK分詞器提供了兩種mapping型別用來做文件的分詞分別是
ik_max_word
和ik_smart
ik_max_word 和 ik_smart 什麼區別?
ik_max_word: 會將文字做最細粒度的拆分
,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,中華人民,中華,華人,人民共和國,人民,人,民,共和國,共和,和,國國,國歌”,會窮盡各種可能的組合;
ik_smart: 會做最粗粒度的拆分
,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,國歌”。
測試資料
DELETE /ems
PUT /ems
{
"mappings":{
"_doc":{
"properties":{
"name":{
"type":"text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"age":{
"type":"integer"
},
"bir":{
"type":"date"
},
"content":{
"type":"text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"address":{
"type":"keyword"
}
}
}
}
}
PUT /ems/_doc/_bulk
{"index":{}}
{"name":"小黑","age":23,"bir":"2012-12-12","content":"為開發團隊選擇一款優秀的MVC框架是件難事兒,在眾多可行的方案中決擇需要很高的經驗和水平","address":"北京"}
{"index":{}}
{"name":"王小黑","age":24,"bir":"2012-12-12","content":"Spring 框架是一個分層架構,由 7 個定義良好的模組組成。Spring 模組構建在核心容器之上,核心容器定義了建立、配置和管理 bean 的方式","address":"上海"}
{"index":{}}
{"name":"張小五","age":8,"bir":"2012-12-12","content":"Spring Cloud 作為Java 語言的微服務框架,它依賴於Spring Boot,有快速開發、持續交付和容易部署等特點。Spring Cloud 的元件非常多,涉及微服務的方方面面,井在開源社群Spring 和Netflix 、Pivotal 兩大公司的推動下越來越完善","address":"無錫"}
{"index":{}}
{"name":"win7","age":9,"bir":"2012-12-12","content":"Spring的目標是致力於全方位的簡化Java開發。 這勢必引出更多的解釋, Spring是如何簡化Java開發的?","address":"南京"}
{"index":{}}
{"name":"梅超風","age":43,"bir":"2012-12-12","content":"Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API","address":"杭州"}
{"index":{}}
{"name":"張無忌","age":59,"bir":"2012-12-12","content":"ElasticSearch是一個基於Lucene的搜尋伺服器。它提供了一個分散式多使用者能力的全文搜尋引擎,基於RESTful web介面","address":"北京"}
GET /ems/_doc/_search
{
"query":{
"term":{
"content":"框架"
}
},
"highlight": {
"pre_tags": ["<span style='color:red'>"],
"post_tags": ["</span>"],
"fields": {
"*":{}
}
}
}
11.4 配置擴充套件詞
IK支援自定義
擴充套件詞典
和停用詞典
,所謂擴充套件詞典
就是有些詞並不是關鍵詞,但是也希望被ES用來作為檢索的關鍵詞,可以將這些詞加入擴充套件詞典。停用詞典
就是有些詞是關鍵詞,但是出於業務場景不想使用這些關鍵詞被檢索到,可以將這些詞放入停用詞典。如何定義擴充套件詞典和停用詞典可以修改IK分詞器中
config
目錄中IKAnalyzer.cfg.xml
這個檔案。NOTE:詞典的編碼必須為UTF-8,否則無法生效
1. 修改vim IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴充套件配置</comment>
<!--使用者可以在這裡配置自己的擴充套件字典 -->
<entry key="ext_dict">ext_dict.dic</entry>
<!--使用者可以在這裡配置自己的擴充套件停止詞字典-->
<entry key="ext_stopwords">ext_stopword.dic</entry>
</properties>
2. 在ik分詞器目錄下config目錄中建立ext_dict.dic檔案 編碼一定要為UTF-8才能生效
vim ext_dict.dic 加入擴充套件詞即可
3. 在ik分詞器目錄下config目錄中建立ext_stopword.dic檔案
vim ext_stopword.dic 加入停用詞即可
4.重啟es生效
12. (過濾查詢) Filter Query
12.1 過濾查詢
其實準確來說,ES中的查詢操作分為2種:
查詢(query)
和過濾(filter)
。查詢即是之前提到的query查詢,它 (查詢)預設會計算每個返回文件的得分,然後根據得分排序
。而過濾(filter)只會篩選出符合的文件,並不計算 得分,且它可以快取文件 。所以,單從效能考慮,過濾比查詢更快
。換句話說,過濾適合在大範圍篩選資料,而查詢則適合精確匹配資料。一般應用時, 應先使用過濾操作過濾資料, 然後使用查詢匹配資料。
12.2 過濾語法
GET /ems/_doc/_search
{
"query": {
"bool": {
"must": [
{"match_all": {}}
],
"filter": {
"range": {
"age": {
"gte": 10
}
}
}
}
}
}
NOTE: 在執行filter和query時,先執行filter在執行query
NOTE:Elasticsearch會自動快取經常使用的過濾器,以加快效能。
12.3 常見的過濾器型別
term 、 terms Filter
GET /ems/_doc/_search # 使用term過濾
{
"query": {
"bool": {
"must": [
{"term": {
"name": {
"value": "小黑"
}
}}
],
"filter": {
"term": {
"content":"框架"
}
}
}
}
}
GET /dangdang/_doc/_search #使用terms過濾
{
"query": {
"bool": {
"must": [
{"term": {
"name": {
"value": "中國"
}
}}
],
"filter": {
"terms": {
"content":[
"科技",
"聲音"
]
}
}
}
}
}
ranage filter
GET /ems/_doc/_search
{
"query": {
"bool": {
"must": [
{"term": {
"name": {
"value": "中國"
}
}}
],
"filter": {
"range": {
"age": {
"gte": 7,
"lte": 20
}
}
}
}
}
}
exists filter
過濾存在指定欄位,獲取欄位不為空的索引記錄使用
GET /ems/_doc/_search
{
"query": {
"bool": {
"must": [
{"term": {
"name": {
"value": "中國"
}
}}
],
"filter": {
"exists": {
"field":"aaa"
}
}
}
}
}
ids filter
過濾含有指定欄位的索引記錄
GET /ems/_doc/_search
{
"query": {
"bool": {
"must": [
{"term": {
"name": {
"value": "中國"
}
}}
],
"filter": {
"ids": {
"values": ["1","2","3"]
}
}
}
}
}
13. Java操作ES
13.1 引入maven依賴
<!-- high client-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.0</version>
<exclusions>
<exclusion>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.0</version>
</dependency>
<!--rest low client-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>7.6.0</version>
</dependency>
13.2建立索引和型別
Rest的建立方式
// 1.在restful的建立方式
PUT /dangdang
{
"mappings": {
"book":{
"properties": {
"name":{
"type":"text",
"analyzer": "ik_max_word"
},
"age":{
"type":"integer"
},
"sex":{
"type":"keyword"
},
"content":{
"type":"text",
"analyzer": "ik_max_word"
}
}
}
}
}
Java中建立方式
/**
* 建立索引並建立型別同時指定對映
*/
@Test
public void testCreateIndexAndTypeAndMapping() throws IOException, ExecutionException, InterruptedException {
System.out.println("=======建立索引=======");
CreateIndexRequest indexRequest = new CreateIndexRequest("dangdang");
CreateIndexResponse indexResponse = client.indices().create(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse.index());
System.out.println("=======建立型別指定對映=======");
XContentBuilder xContentBuilder = XContentFactory.jsonBuilder();
xContentBuilder.startObject("properties").startObject("name")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("age")
.field("type", "integer")
.endObject()
.startObject("sex")
.field("type", "keyword")
.endObject()
.startObject("content")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.endObject()
.endObject();
PutMappingRequest putMappingRequest = new PutMappingRequest("dangdang").source(xContentBuilder);
client.indices().putMapping(putMappingRequest);
}
13.3 索引一條記錄
/**
* 新增文件 手動指定id
*
* @param indexName 索引名稱
* @param id 文件id
* @param source 文件資料
* @return
* @throws IOException
*/
public IndexResponse addDoc(String indexName, String id, String source) throws IOException {
IndexRequest request = new IndexRequest(indexName);
if (null != id) {
request.id(id);
}
request.source(source, XContentType.JSON);
return client.index(request, RequestOptions.DEFAULT);
}
13.3 更新一條索引
/**
* 根據 id 更新指定索引中的文件
*
* @param indexName 索引名稱
* @param id 文件id
* @return
* @throws IOException
*/
public UpdateResponse updateDoc(String indexName, String id, String updateJson) throws IOException {
UpdateRequest request = new UpdateRequest(indexName, id);
request.doc(updateJson, XContentType.JSON);
return client.update(request, RequestOptions.DEFAULT);
}
13.4 刪除一條索引
/**
* 根據 id 刪除指定索引中的文件
*
* @param indexName 索引名稱
* @param id 文件id
* @return
* @throws IOException
*/
public DeleteResponse deleteDoc(String indexName, String id) throws IOException {
DeleteRequest request = new DeleteRequest(indexName, id);
return client.delete(request, RequestOptions.DEFAULT);
}
13.5 批量更新
/**
* 批量更新
*/
public void testBulk() throws IOException {
//新增第一條記錄
IndexRequest request1 = new IndexRequest("dangdang");
request1.id("1").source(XContentFactory.jsonBuilder().startObject().field("name", "中國科技").field("age", 23).field("sex", "男").field("content", "這是個好人").endObject());
//新增第二條記錄
IndexRequest request2 = new IndexRequest("dangdang", "2");
request2.source(XContentFactory.jsonBuilder().startObject().field("name", "中國之聲").field("age", 23).field("sex", "男").field("content", "這是一個好的聲音").endObject());
//更新記錄
UpdateRequest updateRequest = new UpdateRequest("dangdang", "1");
updateRequest.doc(XContentFactory.jsonBuilder().startObject().field("name", "中國力量").endObject());
//刪除一條記錄
DeleteRequest deleteRequest = new DeleteRequest("dangdang", "1");
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.add(request1).add(request2).add(updateRequest).add(deleteRequest);
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
BulkItemResponse[] items = bulk.getItems();
for (BulkItemResponse item : items) {
System.out.println(item.status());
}
}
13.6 檢索記錄
查詢所有並排序
/**
* 查詢所有並排序
* ASC 升序 DESC 降序
* .sort("age", SortOrder.ASC) 指定排序欄位以及使用哪種方式排序
* .sort("age", SortOrder.DESC) 指定排序欄位以及使用哪種方式排序
*/
@Test
public void testMatchAllQuery() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(new MatchAllQueryBuilder()).sort("age", SortOrder.ASC);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
System.out.println("符合條件的記錄數: " + hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println("當前索引的分數: " + hit.getScore());
System.out.println("對應結果:=====>" + hit.getSourceAsString());
System.out.println("指定欄位結果:" + hit.getSourceAsMap().get("name"));
System.out.println("=================================================");
}
}
分頁查詢
/**
* 分頁查詢
* From 從那條記錄開始 預設從0 開始 form = (pageNow-1)*size
* Size 每次返回多少條符合條件的結果 預設10
*/
@Test
public void testMatchAllQuery() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(new MatchAllQueryBuilder()).sort("age", SortOrder.ASC);
searchSourceBuilder.from(0);
searchSourceBuilder.size(5);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
System.out.println("符合條件的記錄數: " + hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println("當前索引的分數: " + hit.getScore());
System.out.println("對應結果:=====>" + hit.getSourceAsString());
System.out.println("指定欄位結果:" + hit.getSourceAsMap().get("name"));
System.out.println("=================================================");
}
}
查詢返回欄位
/**
* 查詢返回指定欄位(source) 預設返回所有
* fetchSource 引數1:包含哪些欄位 引數2:排除哪些欄位
* fetchSource("*","age") 返回所有欄位中排除age欄位
* fetchSource("name","") 只返回name欄位
* fetchSource(new String[]{},new String[]{})
*/
@Test
public void testMatchAllQuery() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(new MatchAllQueryBuilder()).sort("age", SortOrder.ASC);
searchSourceBuilder.from(0);
searchSourceBuilder.size(5);
searchSourceBuilder.fetchSource("*","age");
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
System.out.println("符合條件的記錄數: " + hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println("當前索引的分數: " + hit.getScore());
System.out.println("對應結果:=====>" + hit.getSourceAsString());
System.out.println("指定欄位結果:" + hit.getSourceAsMap().get("name"));
System.out.println("=================================================");
}
}
term查詢
/**
* term查詢
*/
@Test
public void testTerm() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("name","小黑"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
range查詢
/**
* rang查詢
* lt 小於
* lte 小於等於
* gt 大於
* gte 大於等於
*/
@Test
public void testRange() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.rangeQuery("age").lt(40).gte(8)).sort("age", SortOrder.ASC);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
prefix查詢
/**
* prefix 字首查詢
*/
@Test
public void testPrefix() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.prefixQuery("name", "張"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
wildcard查詢
/**
* wildcardQuery 萬用字元查詢
*
*/
@Test
public void testwildcardQuery() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.wildcardQuery("name", "張*"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
Ids查詢
/**
* ids 查詢
*/
@Test
public void testIds() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.idsQuery().addIds("R2t-9HYBj6awZSXQsOLH", "Rmt-9HYBj6awZSXQsOLH"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
fuzzy模糊查詢
/**
* fuzzy 查詢
*/
@Test
public void testFuzzy() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.fuzzyQuery("content", "開發"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
bool 查詢
/**
* bool 查詢
*/
@Test
public void testBool() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(QueryBuilders.matchAllQuery());
boolQueryBuilder.mustNot(QueryBuilders.rangeQuery("age").lte(8));
boolQueryBuilder.must(QueryBuilders.termQuery("name","中國"));
searchSourceBuilder.query(boolQueryBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
System.out.println("符合條件的記錄數: " + hits.getTotalHits());
for (SearchHit hit : hits) {
System.out.println("當前索引的分數: " + hit.getScore());
System.out.println("對應結果:=====>" + hit.getSourceAsString());
System.out.println("指定欄位結果:" + hit.getSourceAsMap().get("name"));
System.out.println("=================================================");
}
}
高亮查詢
/**
* 高亮查詢
* .highlighter(highlightBuilder) 用來指定高亮設定
* requireFieldMatch(false) 開啟多個欄位高亮
* field 用來定義高亮欄位
* preTags("<span style='color:red'>") 用來指定高亮字首
* postTags("</span>") 用來指定高亮字尾
*/
@Test
public void testHighlight() throws IOException {
SearchRequest searchRequest = new SearchRequest("ems");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//searchSourceBuilder.query(QueryBuilders.termQuery("name", "win"));
searchSourceBuilder.query(QueryBuilders.termQuery("content", "框架"));
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.requireFieldMatch(false).field("name").field("content").preTags("<span style='color:red'>").postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHits hits = searchResponse.getHits();
System.out.println("符合條件的記錄數: "+hits.getTotalHits());
for (SearchHit hit : hits) {
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
System.out.println("================高亮之前==========");
for(Map.Entry<String,Object> entry:sourceAsMap.entrySet()){
System.out.println("key: "+entry.getKey() +" value: "+entry.getValue());
}
System.out.println("================高亮之後==========");
for (Map.Entry<String,Object> entry:sourceAsMap.entrySet()){
HighlightField highlightField = highlightFields.get(entry.getKey());
if (highlightField!=null){
System.out.println("key: "+entry.getKey() +" value: "+ highlightField.fragments()[0]);
}else{
System.out.println("key: "+entry.getKey() +" value: "+entry.getValue());
}
}
}
}
多欄位查詢
MultiMatchQueryBuilder queryBuilder
= QueryBuilders.multiMatchQuery("框架","content","name");
多欄位分詞查詢
QueryStringQueryBuilder queryStringQueryBuilder =
QueryBuilders.queryStringQuery("框架張無忌")
.analyzer("ik_max_word") //定義分詞器
.field("name")//定義欄位
.field("content");//欄位
14. SpringBoot Data操作ES
14.1 引入依賴
<!--通過spring data 操作Es-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
14.2 編寫yml配置
spring:
data:
elasticsearch:
cluster-nodes: localhost:9300
14.3 編寫entity
@Document(indexName = "dangdang",type = "book")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {
@Id
private String id;
@Field(type = FieldType.Text,analyzer ="ik_max_word")
private String name;
@Field(type = FieldType.Date)
private Date createDate;
@Field(type = FieldType.Keyword)
private String author;
@Field(type = FieldType.Text,analyzer ="ik_max_word")
private String content;
}
@Document
: 代表一個文件記錄
indexName
: 用來指定索引名稱
type
: 用來指定索引型別
@Id
: 用來將物件中id和ES中_id對映
@Field
: 用來指定ES中的欄位對應Mapping
type
: 用來指定ES中儲存型別
analyzer
: 用來指定使用哪種分詞器
14.4 編寫BookRepository
public interface BookRepository extends ElasticsearchRepository<Book,String> {
}
14.5 索引or更新一條記錄
NOTE:這種方式根據實體類中中配置自動在ES建立索引,型別以及對映
@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class TestSpringBootDataEs {
@Autowired
private BookRepository bookRespistory;
/**
* 新增索引和更新索引 id 存在更新 不存在新增
*/
@Test
public void testSaveOrUpdate(){
Book book = new Book();
book.setId("21");
book.setName("小陳");
book.setCreateDate(new Date());
book.setAuthor("李白");
book.setContent("這是中國的好人,這真的是一個很好的人,李白很狂");
bookRespistory.save(book);
}
}
14.6 刪除一條記錄
/**
* 刪除一條索引
*/
@Test
public void testDelete(){
Book book = new Book();
book.setId("21");
bookRespistory.delete(book);
}
14.7 查詢
/**
* 查詢所有
*/
@Test
public void testFindAll(){
Iterable<Book> books = bookRespistory.findAll();
for (Book book : books) {
System.out.println(book);
}
}
/**
* 查詢一個
*/
@Test
public void testFindOne(){
Optional<Book> byId = bookRespistory.findById("21");
System.out.println(byId.get());
}
14.8 查詢排序
/**
* 排序查詢
*/
@Test
public void testFindAllOrder(){
Iterable<Book> books = bookRespistory.findAll(Sort.by(Sort.Order.asc("createDate")));
books.forEach(book -> System.out.println(book) );
}
14.9 自定義基本查詢
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And |
findByNameAndPrice |
{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or |
findByNameOrPrice |
{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is |
findByName |
{"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not |
findByNameNot |
{"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between |
findByPriceBetween |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual |
findByPriceLessThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual |
findByPriceGreaterThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before |
findByPriceBefore |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After |
findByPriceAfter |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like |
findByNameLike |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith |
findByNameStartingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith |
findByNameEndingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing |
findByNameContaining |
{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In |
findByNameIn (Collection<String>names)
|
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn (Collection<String>names)
|
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near |
findByStoreNear |
Not Supported Yet ! |
True |
findByAvailableTrue |
{"bool" : {"must" : {"field" : {"available" : true}}}} |
False |
findByAvailableFalse |
{"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy |
findByAvailable TrueOrderByNameDesc
|
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
public interface BookRepository extends ElasticsearchRepository<Book,String> {
//根據作者查詢
List<Book> findByAuthor(String keyword);
//根據內容查詢
List<Book> findByContent(String keyword);
//根據內容和名字查
List<Book> findByNameAndContent(String name,String content);
//根據內容或名稱查詢
List<Book> findByNameOrContent(String name,String content);
//範圍查詢
List<Book> findByPriceBetween(Double start,Double end);
//查詢名字以xx開始的
List<Book> findByNameStartingWith(String name);
//查詢某個欄位值是否為false
List<Book> findByNameFalse();
//.......
}
14.10 實現複雜查詢
自定義介面
public interface CustomerBookRepository {
//實現分頁的方法
List<Book> findByPageable(int page,int size);
//term查詢高亮
List<Book> findByNameAndHighlightAdnPageable(String name,int page,int size,String filter);
}
自定義實現
@Configuration
public class CustomerBookRepositoryImpl implements CustomerBookRepository{
@Autowired
private ElasticsearchT_doclate elasticsearchT_doclate;
@Override
public List<Book> findByNameAndHighlightAdnPageable(String name, int page, int size,String filter) {
HighlightBuilder.Field nameField = new HighlightBuilder
.Field("*")
.preTags("<span style='color:red'>")
.postTags("</span>").requireFieldMatch(false);
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(name,"name","content"))
.withPageable(PageRequest.of(page,size))
.withHighlightFields(nameField)
.withFilter(boolQuery().mustNot(termQuery("name",filter)))
.build();
AggregatedPage<Book> books = elasticsearchT_doclate.queryForPage(nativeSearchQuery, Book.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
SearchHits searchHits = response.getHits();
SearchHit[] hits = searchHits.getHits();
ArrayList<Book> books = new ArrayList<Book>();
for (SearchHit hit : hits) {
Book book = new Book();
//原始map
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
book.setId(sourceAsMap.get("id").toString());
book.setAuthor(sourceAsMap.get("author").toString());
book.setPrice(Double.parseDouble(sourceAsMap.get("price").toString()));
book.setCreateDate(new Date(Long.valueOf(sourceAsMap.get("createDate").toString())));
book.setName(sourceAsMap.get("name").toString());
book.setContent(sourceAsMap.get("content").toString());
//高亮
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
System.out.println(highlightFields);
if (highlightFields.get("name") != null) {
String nameHighlight = highlightFields.get("name").getFragments()[0].toString();
book.setName(nameHighlight);
}
if (highlightFields.get("content") != null) {
String contentHighlight = highlightFields.get("content").getFragments()[0].toString();
book.setContent(contentHighlight);
}
books.add(book);
}
return new AggregatedPageImpl<T>((List<T>)books);
}
});
return books.getContent();
}
@Override
public List<Book> findByPageable(int page, int size) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("dangdang")
.withTypes("book")
.withQuery(matchAllQuery())
.withPageable(PageRequest.of(page,size))
.build();
return elasticsearchT_doclate.queryForList(searchQuery,Book.class);
}
}
15. ES中叢集
15.1 相關概念
叢集(cluster)
一個叢集就是由一個或多個節點組織在一起,它們共同持有你整個的資料,並一起提供索引和搜尋功能。一個叢集 由一個唯一的名字標識,這個名字預設就是
elasticsearch
。這個名字是重要的,因為一個節點只能通過指定某個叢集的名字,來加入這個叢集。在產品環境中顯式地設定這個名字是一個好習慣,但是使用預設值來進行測試/開發也是不錯的。
節點(node)
一個節點是你叢集中的一個伺服器,作為叢集的一部分,它儲存你的資料,參與叢集的索引和搜尋功能。和叢集類似,一個節點也是由一個名字來標識的,預設情況下,這個名字是一個隨機的漫威漫畫角色的名字,這個名字會在啟動的時候賦予節點。這個名字對於管理工作來說挺重要的,因為在這個管理過程中,你會去確定網路中的哪些伺服器對應於Elasticsearch叢集中的哪些節點。
一個節點可以通過配置叢集名稱的方式來加入一個指定的叢集。預設情況下,每個節點都會被安排加入到一個叫 做“elasticsearch”的叢集中,這意味著,如果你在你的網路中啟動了若干個節點,並假定它們能夠相互發現彼此,它們將會自動地形成並加入到一個叫做“elasticsearch”的叢集中。
在一個叢集裡,只要你想,可以擁有任意多個節點。而且,如果當前你的網路中沒有執行任何Elasticsearch節點, 這時啟動一個節點,會預設建立並加入一個叫做“elasticsearch”的叢集。
分片和複製(shards & replicas)
一個索引可以儲存超出單個結點硬體限制的大量資料。比如,一個具有10億文件的索引佔據1TB的磁碟空間,而任一節點都沒有這樣大的磁碟空間;或者單個節點處理搜尋請求,響應太慢。為了解決這個問題,Elasticsearch提供了將索引劃分成多份的能力,這些份就叫做分片。當你建立一個索引的時候,你可以指定你想要的分片的數量。每個分片本身也是一個功能完善並且獨立的“索引”,這個“索引”可以被放置 到叢集中的任何節點上。 分片之所以重要,主要有兩方面的原因:
允許你水平分割/擴充套件你的內容容量允許你在分片(潛在地,位於多個節點上)之上進行分散式的、並行的操作,進而提高效能/吞吐量 至於一個分片怎樣分佈,它的文件怎樣聚合回搜尋請求,是完全由Elasticsearch管理的,對於作為使用者的你來說,這些都是透明的。
在一個網路/雲的環境裡,失敗隨時都可能發生,在某個分片/節點不知怎麼的就處於離線狀態,或者由於任何原因消失了。這種情況下,有一個故障轉移機制是非常有用並且是強烈推薦的。為此目的,Elasticsearch允許你建立分片的一份或多份拷貝,這些拷貝叫做複製分片,或者直接叫複製。複製之所以重要,主要有兩方面的原因:
在分片/節點失敗的情況下,提供了高可用性。因為這個原因,注意到複製分片從不與原/主要 (original/primary)分片置於同一節點上是非常重要的。 擴充套件你的搜尋量/吞吐量,因為搜尋可以在所有的複製上並行執行
總之,每個索引可以被分成多個分片。一個索引也可以被複制0次(意思是沒有複製)或多次。一旦複製了,每個 索引就有了主分片(作為複製源的原來的分片)和複製分片(主分片的拷貝)之別。分片和複製的數量可以在索引建立的時候指定。在索引建立之後,你可以在任何時候動態地改變複製數量,但是不能改變分片的數量。
預設情況下,
Elasticsearch中的每個索引被分片5個主分片和1個複製
,這意味著,如果你的叢集中至少有兩個節點,你的索引將會有5個主分片和另外5個複製分片(1個完全拷貝),這樣的話每個索引總共就有10個分片。一個索引的多個分片可以存放在叢集中的一臺主機上,也可以存放在多臺主機上,這取決於你的叢集機器數量。主分片和複製分片的具體位置是由ES內在的策略所決定的。
15.2 快速搭建叢集
1. 將原有ES安裝包複製三份
cp -r elasticsearch-7.6.0/ master/
cp -r elasticsearch-7.6.0/ slave1/
cp -r elasticsearch-7.6.0/ slave2/
2. 刪除複製目錄中data目錄
#注意:由於複製目錄之前使用過因此需要在建立叢集時將原來資料刪除
rm -rf master/data
rm -rf slave1/data
rm -rf slave2/data
3. 編輯沒有資料夾中config目錄中jvm.options檔案跳轉啟動記憶體
vim master/config/jvm.options
vim slave1/config/jvm.options
vim slave2/config/jvm.options
#分別加入: -Xms512m -Xmx512m
4. 分別修改三個資料夾中config目錄中elasticsearch.yml檔案
vim master/config/elasticsearch.yml
vim salve1/config/elasticsearch.yml
vim slave2/config/elasticsearch.yml
#分別修改如下配置:
cluster.name: my-es #叢集名稱(叢集名稱必須一致)
node.name: es-03 #節點名稱(節點名稱不能一致)
network.host: 0.0.0.0 #監聽地址(必須開啟遠端許可權,並關閉防火牆)
http.port: 9200 #監聽埠(在一臺機器時服務埠不能一致)
discovery.zen.ping.unicast.hosts: ["localhost:9301", "localhost:9302"] #另外兩個節點的ip
gateway.recover_after_nodes: 3 #叢集可做master的最小節點數
transport.tcp.port: 9300 #叢集TCP埠(在一臺機器搭建必須修改)
5. 啟動多個es
./master/bin/elasticsearch
./slave1/bin/elasticsearch
./slave2/bin/elasticsearch
6. 檢視節點狀態
curl http://10.102.115.3:9200
curl http://10.102.115.3:8200
curl http://10.102.115.3:7200
7. 檢視叢集健康
http://10.102.115.3:9200/_cat/health?v
15.3 安裝head外掛(視覺化外掛)
1. 訪問github網站
搜尋: elasticsearch-head 外掛
2. 安裝git
yum install git
3. 將elasticsearch-head下載到本地
git clone git://github.com/mobz/elasticsearch-head.git
4. 安裝nodejs
#注意: 沒有wget的請先安裝yum install -y wget
wget http://cdn.npm.taobao.org/dist/node/latest-v8.x/node-v8.1.2-linux-x64.tar.xz
5. 解壓縮nodejs
xz -d node-v10.15.3-linux-arm64.tar.xz
tar -xvf node-v10.15.3-linux-arm64.tar
6. 配置環境變數
mv node-v10.15.3-linux-arm64 nodejs
mv nodejs /usr/nodejs
vim /etc/profile
export NODE_HOME=/usr/nodejs
export PATH=$PATH:$JAVA_HOME/bin:$NODE_HOME/bin
source /etc/profile
7. 進入elasticsearch-head的目錄
npm config set registry https://registry.npm.taobao.org
npm install
npm run start
8. 編寫elastsearch.yml配置檔案開啟head外掛的訪問
http.cors.enabled: true
http.cors.allow-origin: "*"
9. 啟動訪問head外掛 預設埠9100
http://ip:9100 檢視叢集狀態