1. 程式人生 > 其它 >NOI Online 2022 提高組

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_tagspost_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  檢視叢集狀態

http://192.168.8.112:5601

https://www.cnblogs.com/reycg-blog/p/9931482.html

https://blog.csdn.net/UbuntuTouch/article/details/99481016