1. 基本概念
Elasticsearch有幾個核心概念,先理解這些概念將有助於掌握Elasticsearch。
近實時(Near Realtime / NRT)
Elasticsearch是一個近實時的搜索平臺,從生成文檔索引到文檔成爲可搜索,有一個輕微的延遲(通常是一秒鐘)。
集羣(Cluster)
ES 默認就是集羣狀態,整個集羣是一份完整、互備的數據。
集羣是一個或多個節點(服務器)的集合。集羣中的節點一起存儲數據,對外提供搜索功能。集羣由一個唯一的名稱標識,該名稱默認是“elasticsearch”。集羣名稱很重要,節點都是通過集羣名稱加入集羣。
集羣不要重名,取名一般要有明確意義,否則會引起混亂。例如,開發、測試和生產集羣的名稱可以使用logging-dev、logging-test和logging-prod。
集羣節點數不受限制,可以只有一個節點。
節點(Node)
節點是一個服務器,屬於某個集羣。節點存儲數據,參與集羣的索引和搜索功能。與集羣一樣,節點也是通過名稱來標識的。默認情況下,啓動時會分配給節點一個UUID(全局惟一標識符)作爲名稱。如有需要,可以給節點取名,通常取名時應考慮能方便識別和管理。
默認情況下,節點加入名爲elasticsearch的集羣,通過設置節點的集羣名,可加入指定集羣。
索引(Index)
索引是具有某種特徵的文檔集合,相當於一本書的目錄。例如,可以爲客戶數據建立索引,爲訂單數據建立另一個索引。索引由名稱標識(必須全部爲小寫),可以使用該名稱,對索引中的文檔進行建立索引、搜索、更新和刪除等操作。一個集羣中,索引數量不受限制。
類似於rdbms的database(5.x), 對於用戶來說是一個邏輯數據庫,雖然物理上會被分多個shard存放,也可能存放在多個node中。 6.x 7.x index相當於table
類型(Type)
類似於rdbms的table,但是與其說像table,其實更像面向對象中的class , 同一Json的格式的數據集合。(6.x只允許建一個,7.0被廢棄,造成index實際相當於table級)
文檔(Document)
文檔是可以建立索引的基本信息單元,相當於書的具體章節。
例如,可以爲單個客戶創建一個文檔,爲單個訂單創建另一個文檔。文檔用JSON (JavaScript對象表示法)表示。在索引中,理論上可以存儲任意數量的文檔。
類似於rdbms的 row、面向對象裏的object
字段|屬性(Field)
相當於字段、屬性
分片與副本(Shards & Replicas)
索引可能存儲大量數據,數據量可能超過單個節點的硬件限制。
例如,一個索引包含10億個文檔,將佔用1TB的磁盤空間,單個節點的磁盤放不下。
Elasticsearch提供了索引分片功能,創建索引時,可以定義所需的分片數量。每個分片本身都是一個功能齊全,獨立的“索引”,可以託管在集羣中的任何節點上。
分片之所以重要,主要有2個原因:
- 允許水平切分內容,以便內容可以存儲到普通的服務器中
- 允許跨分片操作(如查詢時,查詢多個分片),提高性能/吞吐量
分片如何部署、如何跨片搜索完全由Elasticsearch管理,對外是透明的。
網絡環境隨時可能出現故障,如果某個分片/節點由於某種原因離線或消失,那麼使用故障轉移機制是非常有用的,強烈建議使用這種機制。爲此,Elasticsearch允許爲分片創建副本。
副本之所以重要,主要有2個原因:
- 在分片/節點失敗時提供高可用性。因此,原分片與副本不應放在同一個節點上。
- 擴展吞吐量,因爲可以在所有副本上並行執行搜索。
總而言之,索引可以分片,索引分片可以創建副本。複製後,每個索引將具有主分片與副本分片。
創建索引時,可以爲每個索引定義分片和副本的數量。之後,還可以隨時動態更改副本數量。您可以使用_shrink和_split api更改現有索引的分片數量,但動態修改副本數量相當麻煩,最好還是預先計劃好分片數量。
默認情況下,Elasticsearch中的每個索引分配一個主分片和一個副本(7.X之前,默認是5片,副本是0。7.X默認改爲1片,副本爲1)。如果集羣中有兩個節點,就可以將索引主分片部署在一個節點,副本分片放在另一個節點,提高可用性。
概念之間關係圖
這張圖可以展示出ES各組件之間的關係,整張表是一個Cluster,橫行是Nodes,豎列是Indices,深綠色方塊是Primary Shards,淺綠色方塊是Replica Shards。
至於單個Host上的Node數目問題,在硬件資源有限的情況下,一般的做法是一個Host只運行一個ES進程,也就是一個Node。例外情況是,由於ES內存配置上的特殊要求(JVM
Heap不能超過32G),如果你的Host特別NB(16 Core CPU + 128G RAM + SSD 這種),完全可以在單個Host上運行多個ES進程以避免硬件資源的浪費。
ES概念和MySQL關係對比
MySQL |
ES5.X |
ES6.X |
ES7.X |
Database |
Index |
|
|
Table |
Type |
Index(Type成了擺設) |
Index(Type被移除掉) |
Row |
Document |
Document |
|
Column |
Field |
Field |
|
Elasticsearch數據存儲方式
數據的插入用PUT,用文檔的方式進行數據的存儲;
1)面向文檔
Elasticsearch是面向文檔(document oriented)的,這意味着它可以存儲整個對象或文檔(document)。然而它不僅僅是存儲,還會索引(index)每個文檔的內容使之可以被搜索。在Elasticsearch中,你可以對文檔(而非成行成列的數據)進行索引、搜索、排序、過濾。這種理解數據的方式與以往完全不同,這也是Elasticsearch能夠執行復雜的全文搜索的原因之一。
面向對象(表)是基於二維表(行列excel)的存儲模型-如mysql,操作數據時把它當做一個對象,創建封裝對象;寫sql語句,查詢等
面向對象:表
id name age
1 kris 22
2 alex 18
面向文檔:單條
----------------------------------------------
{"id" : 1,"name" : "kris", "age" : 22}
-----------------------------------------------
{"id" : 2,"name" : "sher", "age" : 18}
-----------------------------------------------
{"id" : 3,"name" : "hhhh", "age" : 23}
面向文檔(單條)是一條一條的,多個文檔在一塊,以單條數據位單位;json字符串,可直接進行操作如.split(“ ”);遍歷等
2)JSON
ELasticsearch使用Javascript對象符號(JavaScript Object Notation),也就是JSON,作爲文檔序列化格式。JSON現在已經被大多語言所支持,而且已經成爲NoSQL領域的標準格式。它簡潔、簡單且容易閱讀。
更方便的堆數據進行操作;更方便的分詞,全文檢索、搜索等
以下使用JSON文檔來表示一個用戶對象:
{
"email": "[email protected]",
"first_name": "John",
"last_name": "Smith",
"info": {
"bio": "Eco-warrior and defender of the weak",
"age": 25,
"interests": [ "dolphins", "whales" ]
},
"join_date": "2014/05/01"
}
存儲結構
5.x
index--->①庫database
type -->②表 table
field-->③字段column
document-->row一行數據;存儲數據的基本單元;
6.0之後
一個index中只能有一個type,
一個type中有多個文檔document,
一個document中有多個field字段,k v形式的json格式
index可以看作一個表:index( index:_doc默認的 )
7.x,type就被取消了
ES6.0之後就不能運行有type2了!!
在Es6.0之後,一個index中只運行有1個type,弱化了表的概念;(有可能把type取消掉,完全的面向文檔)
名詞解釋 索引 index 一個索引就是一個擁有幾分相似特徵的文檔的集合。比如說,你可以有一個客戶數據的索引,另一個產品目錄的索引,還有一個訂單數據的索引。一個索引由一個名字來標識(必須全部是小寫字母的),並且當我們要對對應於這個索引中的文檔進行索引、搜索、更新和刪除的時候,都要使用到這個名字。在一個集羣中,可以定義任意多的索引。 類型 type 在一個索引中,你可以定義一種或多種類型。一個類型是你的索引的一個邏輯上的分類/分區,其語義完全由你來定。通常,會爲具有一組共同字段的文檔定義一個類型。比如說,我們假設你運營一個博客平臺並且將你所有的數據存儲到一個索引中。在這個索引中,你可以爲用戶數據定義一個類型,爲博客數據定義另一個類型,當然,也可以爲評論數據定義另一個類型。 字段Field 相當於是數據表的字段,對文檔數據根據不同屬性進行的分類標識 document 一個document相當於mysql中的一條 一個文檔是一個可被索引的基礎信息單元。比如,你可以擁有某一個客戶的文檔,某一個產品的一個文檔,當然,也可以擁有某個訂單的一個文檔。文檔以JSON(Javascript Object Notation)格式來表示,而JSON是一個到處存在的互聯網數據交互格式。在一個index/type裏面,你可以存儲任意多的文檔。注意,儘管一個文檔,物理上存在於一個索引之中,文檔必須被索引/賦予一個索引的type。
Type可以理解爲關係型數據庫的Table,每個Type中的字段的數據類型,由mapping定義,如果我們在創建Index的時候,沒有設定mapping,系統會自動根據一條數據的格式來推斷出該數據對應的字段類型
假設有如下實體
public class Movie {
String id;
String name;
Double doubanScore;
List<Actor> actorList;
}
public class Actor{
String id;
String name;
}
這兩個對象如果放在關係型數據庫保存,會被拆成2張表,但是ElasticSearch是用一個json來表示一個document。類似豆瓣某個電影詳情頁 https://movie.douban.com/
保存到ES中應該是
{
"id":"1",
"name":"operation red sea",
"doubanScore":"8.5",
"actorList":[
{"id":"1","name":"zhangyi"},
{"id":"2","name":"haiqing"},
{"id":"3","name":"zhanghanyu"}
]
}
2. ElasticSearch RestFulAPI(DSL)
DSL全稱 Domain Specific language,即特定領域專用語言
全局操作
① 查詢集羣健康情況 API:GET /_cat/health?v
?v表示顯示頭信息
集羣的健康狀態有紅、黃、綠三個狀態
- 綠 – 一切正常(集羣功能齊全)
- 黃 – 所有數據可用,但有些副本尚未分配(集羣功能完全)
- 紅 – 有些數據不可用(集羣部分功能)
② 查詢各個節點狀態 API:GET /_cat/nodes?v
對索引的操作
① 查詢各個索引狀態 API:GET /_cat/indices?v
ES中會默認存在一些索引
health |
green(集羣完整) yellow(單點正常、集羣不完整) red(單點不正常) |
status |
是否能使用 |
index |
索引名 |
uuid |
索引統一編號 |
pri |
主節點幾個分片 |
rep |
從節點幾個(副本數) |
docs.count |
文檔數 |
docs.deleted |
文檔被刪了多少 |
store.size |
整體佔空間大小 |
pri.store.size |
主節點佔空間大小 |
② 創建索引API: PUT 索引名?pretty
PUT movie_index?pretty
使用PUT創建名爲“movie_index”的索引。末尾追加pretty,可以漂亮地打印JSON響應(如果有的話)。紅色警告說在7.x分片數會由默認的5改爲1,我們忽略即可
索引名命名要求:
- 僅可能爲小寫字母,不能下劃線開頭
- 不能包括 , /, *, ?, ", <, >, |, 空格, 逗號, #
- 7.0版本之前可以使用冒號:,但不建議使用並在7.0版本之後不再支持
- 不能以這些字符 -, _, + 開頭
- 不能包括 . 或 …
- 長度不能超過 255 個字符
查詢某個索引的分片情況API:GET /_cat/shards/索引名
GET /_cat/shards/movie_index
默認5個分片,1個副本。所以看到一共有10個分片,5個主,每一個主分片對應一個副本,注意:同一個分片的主和副本肯定不在同一個節點上
刪除索引 API: DELETE /索引名
DELETE /movie_index
對文檔進行操作
① 創建文檔
現在向索引movie_index中放入文檔,文檔ID分別爲1,2,3。 API: PUT /索引名/類型名/文檔id
注意:文檔id和文檔中的屬性”id”不是一回事
PUT /movie_index/movie/1
{ "id":100,
"name":"operation red sea",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"zhang yi"},
{"id":2,"name":"hai qing"},
{"id":3,"name":"zhang han yu"}
]
}
PUT /movie_index/movie/2
{
"id":200,
"name":"operation meigong river",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"zhang han yu"}
]
}
PUT /movie_index/movie/3
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang san feng"}
]
}
注意,Elasticsearch並不要求,先要有索引,才能將文檔編入索引。創建文檔時,如果指定索引不存在,將自動創建。默認創建的索引分片是5,副本是1,我們創建的文檔會在其中的某一個分片上存一份,副本
上存一份,所以看到的響應 _shards-total:2
② 根據文檔id查看文檔
API:GET /索引名/類型名/文檔id GET /movie_index/movie/1?pretty
這裏有一個字段found爲真,表示找到了一個ID爲3 的文檔,另一個字段_source,該字段返回完整JSON文檔。
③ 查詢所有文檔
API:GET /索引名/_search, Kinana中默認顯示10條,可以通過size控制
took: 執行查詢花費的時間毫秒數
_shards=>total:搜索了多少個分片(當前表示搜索了全部5個分片)
④ 根據文檔id刪除文檔
API: DELETE /索引名/類型名/文檔id DELETE /movie_index/movie/3
注意:刪除索引和刪除文檔的區別
- 刪除索引是會立即釋放空間的,不存在所謂的“標記”邏輯。
- 刪除文檔的時候,是將新文檔寫入,同時將舊文檔標記爲已刪除。 磁盤空間是否釋放取決於新舊文檔是否在同一個segment file裏面,因此ES後臺的segment merge在合併segment file的過程中有可能觸發舊文檔的物理刪除。
- 也可以手動執行POST /_forcemerge進行合併觸發
⑤ 替換文檔
- PUT(冪等性操作)
當我們通過執行PUT /索引名/類型名/文檔id命令的添加時候,如果文檔id已經存在,那麼再次執行上面的命令,ElasticSearch將替換現有文檔。
PUT /movie_index/movie/3
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang cuishan"}
]
}
文檔 id3 已經存在,會替換原來的文檔內容
- POST(非冪等性操作)
創建文檔時,ID部分是可選的。如果沒有指定,Elasticsearch將生成一個隨機ID,然後使用它來引用文檔。
POST /movie_index/movie/
{
"id":300,
"name":"incident red sea",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"zhang cuishan"}
]
}
⑥ 根據文檔id更新文檔
除了創建和替換文檔外,ES還可以更新文檔中的某一個字段內容。注意,Elasticsearch實際上並沒有在底層執行就地更新,而是先刪除舊文檔,再添加新文檔。
API:
POST /索引名/類型名/文檔id/_update?pretty
{
"doc": { "字段名": "新的字段值" } doc固定寫法
}
需求:把文檔ID爲3中的 name字段更改爲 “kris”
POST /movie_index/movie/3/_update?pretty
{
"doc": {"name":"kris"}
}
⑦ 根據條件更新文檔(瞭解)
##根據條件更新文檔
POST /movie_index/_update_by_query
{
"query": {
"match":{
"actorList.id":1
}
},
"script": {
"lang": "painless",
"source":"for(int i=0;i<ctx._source.actorList.length;i++){if(ctx._source.actorList[i].id==3){ctx._source.actorList[i].name='tttt'}}"
}
}
⑧ 刪除文檔屬性(瞭解)
POST /movie_index/movie/1/_update
{
"script" : "ctx._source.remove('name')"
}
⑨ 根據條件刪除文檔(瞭解)
POST /movie_index/_delete_by_query
{
"query": {
"match_all": {}
}
}
批處理
除了對單個文檔執行創建、更新和刪除之外,ElasticSearch還提供了使用 _bulk API 批量執行上述操作的能力。
API: POST /索引名/類型名/_bulk?pretty
_bulk表示批量操作
注意:Kibana要求批量操作的json內容寫在同一行
需求1:在索引中批量創建兩個文檔
POST /movie_index/movie/_bulk
{"index":{"_id":66}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"kris"}]}
{"index":{"_id":88}}
{"id":300,"name":"incident red sea","doubanScore":5.0,"actorList":[{"id":4,"name":"kris"}]}
需求2:在一個批量操作中,先更新第一個文檔( ID爲66 ),再刪除第二個文檔(ID爲88)
POST /movie_index/movie/_bulk
{"update":{"_id":"66"}}
{"doc": { "name": "shero" } }
{"delete":{"_id":"88"}}
查詢操作
搜索參數傳遞有2種方法
- URI發送搜索參數查詢所有數據
GET /索引名/_search?q=* &pretty 例如:GET /movie_index/_search?q=_id:66
這種方式不太適合複雜查詢場景,瞭解 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html
- 請求體(request body)發送搜索參數查詢所有數據
GET /movie_index/_search
{
"query": {
"match_all": {}
}
}
按條件查詢(全部)
GET movie_index/movie/_search
{
"query":{
"match_all": {}
}
}
按分詞查詢(必須使用分詞text類型)
測試前:將movie_index索引中的數據恢復到初始的3條
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 0.8630463,
"hits" : [
{
"_index" : "movie_index",
"_type" : "movie",
"_id" : "1",
"_score" : 0.8630463,
"_source" : {
"id" : 100,
"name" : "operation red sea",
"doubanScore" : 8.5,
"actorList" : [
{
"id" : 1,
"name" : "zhang yi"
},
{
"id" : 2,
"name" : "hai qing"
},
{
"id" : 3,
"name" : "zhang han yu"
}
]
}
},
{
"_index" : "movie_index",
"_type" : "movie",
"_id" : "3",
"_score" : 0.5753642,
"_source" : {
"id" : 300,
"name" : "incident red sea",
"doubanScore" : 5.0,
"actorList" : [
{
"id" : 4,
"name" : "zhang san feng"
}
]
}
},
{
"_index" : "movie_index",
"_type" : "movie",
"_id" : "2",
"_score" : 0.2876821,
"_source" : {
"id" : 200,
"name" : "operation meigong river",
"doubanScore" : 8.0,
"actorList" : [
{
"id" : 3,
"name" : "zhang han yu"
}
]
}
}
]
}
}
ES中,name屬性會進行分詞,底層以倒排索引的形式進行存儲,對查詢的內容也會進行分詞,然後和文檔的name屬性內容進行匹配,所以命中3次,不過命中的分值不同。
注意:ES底層在保存字符串數據的時候,會有兩種類型text和keyword
text:分詞
keyword:不分詞
按分詞子屬性查詢
GET movie_index/movie/_search
{
"query":{
"match": {"actorList.name":"zhang han yu"}
}
}
返回3 條結果。
按短語查詢(相當於like %短語%)
按短語查詢,不再利用分詞技術,直接用短語在原始數據中匹配
GET movie_index/movie/_search { "query":{ "match_phrase": {"actorList.name":"zhang han yu"} } }
返回2條結果,把演員名包含zhang han yu的查詢出來
通過term精準搜索匹配(必須使用keyword類型)
GET movie_index/movie/_search { "query":{ "term":{ "actorList.name.keyword":"zhang han yu" } } }
返回2條結果,把演員中完全匹配zhang han yu的查詢出來
fuzzy查詢(容錯匹配)
校正匹配分詞,當一個單詞都無法準確匹配,ES通過一種算法對非常接近的單詞也給與一定的評分,能夠查詢出來,但是消耗更多的性能,對中文來講,實現不是特別好。
GET movie_index/movie/_search { "query":{ "fuzzy": {"name":"rad"} } }
返回2個結果,會把 incident red sea 和 operation red sea 匹配上
過濾—先匹配,再過濾
GET movie_index/movie/_search { "query":{ "match": {"name":"red"} }, "post_filter":{ "term": { "actorList.id": 3 } } }
過濾—匹配和過濾同時(推薦使用)
GET movie_index/movie/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "red"
}}
],
"filter": [
{"term": { "actorList.id": "1"}},
{"term": {"actorList.id": "3"}}
]
}
}
}
過濾--按範圍過濾
GET movie_index/movie/_search
{
"query": {
"range": {
"doubanScore": {
"gte": 6,
"lte": 8.5
}
}
}
}
關於範圍操作符
gt |
大於 |
lt |
小於 |
gte |
大於等於 great than or equals |
lte |
小於等於 less than or equals |
排序
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red sea"}
},
"sort":
{
"doubanScore": {
"order": "desc"
}
}
}
分頁查詢
from參數(基於0)指定從哪個文檔序號開始,size參數指定返回多少個文檔,這兩個參數對於搜索結果分頁非常有用。注意,如果沒有指定from,則默認值爲0。
GET movie_index/movie/_search
{
"query": { "match_all": {} },
"from": 1,
"size": 1
}
指定查詢的字段
GET movie_index/movie/_search
{
"query": { "match_all": {} },
"_source": ["name", "doubanScore"]
}
只顯示name 和doubanScore 字段
高亮
對命中的詞進行高亮顯示
GET movie_index/movie/_search
{
"query":{
"match": {"name":"red sea"}
},
"highlight": {
"fields": {"name":{} }
}
}
聚合
聚合提供了對數據進行分組、統計的能力,類似於SQL中Group By和SQL聚合函數。在ElasticSearch中,可以同時返回搜索結果及其聚合計算結果,這是非常強大和高效的。
需求1:取出每個演員共參演了多少部電影
GET movie_index/movie/_search { "aggs": { "myAGG": { "terms": { "field": "actorList.name.keyword" } } } }
- aggs:表示聚合
- myAGG:給聚合取的名字,
- trems:表示分組,相當於groupBy
- field:指定分組字段
需求2:每個演員參演電影的平均分是多少,並按評分排序
GET movie_index/movie/_search
{
"aggs": {
"groupby_actor_id": {
"terms": {
"field": "actorList.name.keyword" ,
"order": {
"avg_score": "desc"
}
},
"aggs": {
"avg_score":{
"avg": {
"field": "doubanScore"
}
}
}
}
}
}
思考:聚合時爲何要加 .keyword後綴?
.keyword 是某個字符串字段,專門儲存不分詞格式的副本,在某些場景中只允許只用不分詞的格式,比如過濾filter比如聚合aggs, 所以字段要加上.keyword的後綴。
分詞
查看英文單詞默認分詞情況
GET _analyze { "text":"hello world" }
按照空格對單詞進行切分
查看中文默認分詞情況
GET _analyze
{
"text":"小米手機"
}
按照每個漢字進行切分
中文分詞器
通過上面的查詢,我們可以看到ES本身自帶的中文分詞,就是單純把中文一個字一個字的分開,根本沒有詞彙的概念。但是實際應用中,用戶都是以詞彙爲條件,進行查詢匹配的,如果能夠把文章以詞彙爲單
位切分開,那麼與用戶的查詢條件能夠更貼切的匹配上,查詢速度也更加快速。
常見的一些開源分詞器對比,我們使用IK分詞器
分詞器 |
優勢 |
劣勢 |
Smart Chinese Analysis |
官方插件 |
中文分詞效果慘不忍睹 |
IKAnalyzer |
簡單易用,支持自定義詞典和遠程詞典 |
詞庫需要自行維護,不支持詞性識別 |
結巴分詞 |
新詞識別功能 |
不支持詞性識別 |
Ansj中文分詞 |
分詞精準度不錯,支持詞性識別 |
對標hanlp詞庫略少,學習成本高 |
Hanlp |
目前詞庫最完善,支持的特性非常多 |
需要更優的分詞效果,學習成本高 |
IK分詞器的安裝及使用
- 解壓zip文件 unzip elasticsearch-analysis-ik-6.6.0.zip -d /opt/module/elasticsearch/plugins/ik
注意
- 使用unzip進行解壓
- -d指定解壓後的目錄
- 必須放到ES的plugins目錄下,並在plugins目錄下創建單獨的目錄
查看: /opt/module/elasticsearch/plugins/ik/conf下的文件,分詞就是將所有詞彙分好放到文件中
分發: xsync /opt/module/elasticsearch/plugins/ik
重啓ES: es.sh stop es.sh start
測試使用
- 默認分詞器
GET movie_index/_analyze { "text": "我是中國人" }
- ik_smart分詞方式
GET movie_index/_analyze
{
"analyzer": "ik_smart",
"text": "我是中國人"
}
- ik_max_word分詞方式
GET movie_index/_analyze { "analyzer": "ik_max_word", "text": "我是中國人" }
自定義詞庫-本地指定
有的時候,詞庫提供的詞並不包含項目中使用到的一些專業術語或者新興網絡用語,需要我們對詞庫進行補充。具體步驟
- 沒有使用自定義詞庫前
GET movie_index/_analyze { "analyzer": "ik_smart", "text": "藍瘦香菇" }
- 通過配置本地目錄直接指定自定義詞庫
修改/opt/module/elasticsearch/plugins/ik/config/中的IKAnalyzer.cfg.xml
名詞概念:停止詞,是由英文單詞:stopword翻譯過來的,原來在英語裏面會遇到很多a,the,or等使用頻率很多的字或詞,常爲冠詞、介詞、副詞或連詞等。 如果搜索引擎要將這些詞都索引的話,那麼幾乎每個網站都會被索引,也就是說工作量巨大。可以毫不誇張的說句,只要是個英文網站都會用到a或者是the。那麼這些英文的詞跟我們中文有什麼關係呢? 在中文網站裏面其實也存在大量的stopword,我們稱它爲停止詞。比如,我們前面這句話,“在”、“裏面”、“也”、“的”、“它”、“爲”這些詞都是停止詞。這些詞因爲使用頻率過高,幾乎每個網頁上都存在,所以搜索引擎開發人員都將這一類詞語全部忽略掉。如果我們的網站上存在大量這樣的詞語,那麼相當於浪費了很多資源。原本可以添加一個關鍵詞,排名就可以上升一名的,爲什麼不留着添加爲關鍵詞呢?停止詞對SEO的意義不是越多越好,而是儘量的減少爲宜。 |
在/opt/module/elasticsearch/plugins/ik/config/當前目錄下創建myword.txt
[kris@hadoop101 config]$ vim myword.txt
藍瘦
藍瘦香菇
分發配置文件以及myword.txt
- xsync /opt/module/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml
- xsync /opt/module/elasticsearch/plugins/ik/config/myword.txt
重啓ES服務 es.sh stop es.sh start
測試分詞效果
自定義詞庫-遠程指定
遠程配置一般是如下流程,我們這裏簡易通過nginx模擬
修改/opt/module/elasticsearch/plugins/ik/config/中的IKAnalyzer.cfg.xml
注意:將本地配置註釋掉
分發配置文件 xsync /opt/module/elasticsearch/plugins/ik/config/IKAnalyzer.cfg.xml
在nginx.conf文件中配置靜態資源路徑
[kris@hadoop101 conf]$ vim nginx.conf
location /fenci{
root es;
}
在/opt/module/nginx/目錄下創建es/fenci目錄,並在es/fenci目錄下創建myword.txt
[kris@hadoop101 es]$ vim /opt/module/nginx/es/fenci/myword.txt
藍瘦
藍瘦香菇
啓動nginx /opt/module/nginx/sbin/nginx
重啓ES服務 es.sh stop es.sh start
測試nginx是否能夠訪問 瀏覽器中打開: http://hadoop101/fenci/myword.txt
測試分詞效果
更新完成後,ES只會對新增的數據用新詞分詞。歷史數據是不會重新分詞的。如果想要歷史數據重新分詞。需要執行:
POST movies_index_chn/_update_by_query?conflicts=proceed
關於mapping
之前說Type可以理解爲關係型數據庫的Table,那每個字段的數據類型是如何定義的呢?
實際上每個Type中的字段是什麼數據類型,由mapping定義,如果我們在創建Index的時候,沒有設定mapping,系統會自動根據一條數據的格式來推斷出該數據對應的字段類型,具體推斷類型如下:
- true/false → boolean
- 1020 → long
- 20.1 → float
- “2018-02-01” → date
- “hello world” → text +keyword
默認只有text會進行分詞,keyword是不會分詞的字符串。mapping除了自動定義,還可以手動定義,但是隻能對新加的、沒有數據的字段進行定義,一旦有了數據就無法再做修改了。
基於中文分詞搭建索引-自動定義mapping
- 直接創建Document
這個時候index不存在,建立文檔的時候自動創建index,同時mapping會自動定義
PUT /movie_chn_1/movie/1
{ "id":1,
"name":"紅海行動",
"doubanScore":8.5,
"actorList":[
{"id":1,"name":"張譯"},
{"id":2,"name":"海清"},
{"id":3,"name":"張涵予"}
]
}
PUT /movie_chn_1/movie/2
{
"id":2,
"name":"湄公河行動",
"doubanScore":8.0,
"actorList":[
{"id":3,"name":"張涵予"}
]
}
PUT /movie_chn_1/movie/3
{
"id":3,
"name":"紅海事件",
"doubanScore":5.0,
"actorList":[
{"id":4,"name":"張三丰"}
]
}
- 查看自動定義的mapping
- 查詢測試
GET /movie_chn_1/movie/_search
{
"query": {
"match": {
"name": "海行"
}
}
}
- 分析結論
上面查詢“海行”命中了三條記錄,是因爲我們在定義的Index的時候,沒有指定分詞器,使用的是默認的分詞器,對中文是按照每個漢字進行分詞的。
基於中文分詞搭建索引-手動定義mapping
- 定義Index,指定mapping
PUT movie_chn_2
{
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
- 向Index中放入Document
PUT /movie_chn_2/movie/1 { "id":1, "name":"紅海行動", "doubanScore":8.5, "actorList":[ {"id":1,"name":"張譯"}, {"id":2,"name":"海清"}, {"id":3,"name":"張涵予"} ] } PUT /movie_chn_2/movie/2 { "id":2, "name":"湄公河行動", "doubanScore":8.0, "actorList":[ {"id":3,"name":"張涵予"} ] } PUT /movie_chn_2/movie/3 { "id":3, "name":"紅海事件", "doubanScore":5.0, "actorList":[ {"id":4,"name":"張三丰"} ] }
- 查看手動定義的mapping
- 查詢測試
GET /movie_chn_2/movie/_search { "query": { "match": { "name": "海行" } } }
- 分析結論
上面查詢沒有命中任何記錄,是因爲我們在創建Index的時候,指定使用ik分詞器進行分詞
索引數據拷貝
ElasticSearch雖然強大,但是卻不能動態修改mapping到時候我們有時候需要修改結構的時候不得不重新創建索引;
ElasticSearch爲我們提供了一個reindex的命令,就是會將一個索引的快照數據copy到另一個索引,默認情況下存在相同的_id會進行覆蓋(一般不會發生,除非是將兩個索引的數據copy到一個索引中),可以使
用POST _reindex命令將索引快照進行copy
POST _reindex
{
"source": {
"index": "my_index_name"
},
"dest": {
"index": "my_index_name_new"
}
}
索引別名 _aliases
索引別名就像一個快捷方式或軟連接,可以指向一個或多個索引,也可以給任何一個需要索引名的API來使用。
創建索引別名
- 創建Index的時候聲明
PUT 索引名
{
"aliases": {
"索引別名": {}
}
}
#創建索引的時候,手動mapping,並指定別名
創建movie_chn_3索引
PUT movie_chn_3
{
"aliases": {
"movie_chn_3_aliase": {}
},
"mappings": {
"movie":{
"properties": {
"id":{
"type": "long"
},
"name":{
"type": "text",
"analyzer": "ik_smart"
},
"doubanScore":{
"type": "double"
},
"actorList":{
"properties": {
"id":{
"type":"long"
},
"name":{
"type":"keyword"
}
}
}
}
}
}
}
爲已存在的索引增加別名
POST _aliases { "actions": [ { "add":{ "index": "索引名", "alias": "索引別名" }} ] }
#給movie_chn_3添加別名
POST _aliases
{
"actions": [
{ "add":{ "index": "movie_chn_3", "alias": "movie_chn_3_a2" }}
]
}
查詢別名列表
GET _cat/aliases?v
使用索引別名查詢
與使用普通索引沒有區別, GET 索引別名/_search
刪除某個索引的別名
POST _aliases
{
"actions": [
{ "remove": { "index": "索引名", "alias": "索引別名" }}
]
}
POST _aliases
{
"actions": [
{ "remove": { "index": "movie_chn_3", "alias": "movie_chn_3_a2" }}
]
}
使用場景
- 給多個索引分組 (例如, last_three_months)
POST _aliases
{
"actions": [
{ "add": { "index": "movie_chn_1", "alias": "movie_chn_query" }},
{ "add": { "index": "movie_chn_2", "alias": "movie_chn_query" }}
]
}
GET movie_chn_query/_search
- 給索引的一個子集創建視圖
相當於給Index加了一些過濾條件,縮小查詢範圍
POST _aliases
{
"actions": [
{
"add":
{
"index": "movie_chn_1",
"alias": "movie_chn_1_sub_query",
"filter": {
"term": { "actorList.id": "4"}
}
}
}
]
}
GET movie_chn_1_sub_query/_search
- 在運行的集羣中可以無縫的從一個索引切換到另一個索引
POST /_aliases { "actions": [ { "remove": { "index": "movie_chn_1", "alias": "movie_chn_query" }}, { "remove": { "index": "movie_chn_2", "alias": "movie_chn_query" }}, { "add": { "index": "movie_chn_3", "alias": "movie_chn_query" }} ] } #整個操作都是原子的,不用擔心數據丟失或者重複的問題
索引模板
索引模板(Index Template),顧名思義就是創建索引的模具,其中可以定義一系列規則來幫助我們構建符合特定業務需求的索引的mappings和settings,通過使用索引模板可以讓我們的索引具備可預知的一致
性。
創建索引模板
PUT _template/template_movie2020 { "index_patterns": ["movie_test*"], "settings": { "number_of_shards": 1 }, "aliases" : { "{index}-query": {}, "movie_test-query":{} }, "mappings": { "_doc": { "properties": { "id": { "type": "keyword" }, "movie_name": { "type": "text", "analyzer": "ik_smart" } } } } }
其中 "index_patterns": ["movie_test*"]的含義就是凡是往movie_test開頭的索引寫入數據時,如果索引不存在,那麼ES會根據此模板自動建立索引。
在 "aliases" 中用{index}表示,獲得真正的創建的索引名。aliases中會創建兩個別名,一個是根據當前索引創建的,另一個是全局固定的別名。
測試
- 向索引中添加數據
POST movie_test_202011/_doc { "id":"333", "name":"zhang3" }
- 查詢Index的mapping,就是使用我們的索引模板創建的
GET movie_test_202011-query/_mapping
- 根據模板中取的別名查詢數據
GET movie_test-query/_search
查看系統中已有的模板清單
GET _cat/templates
查看某個模板詳情
GET _template/template_movie2020
或者
GET _template/template_movie*
使用場景
分割索引
分割索引就是根據時間間隔把一個業務索引切分成多個索引。
比如 把order_info 變成 order_info_20200101,order_info_20200102 …..
這樣做的好處有兩個:
- 結構變化的靈活性
因爲ES不允許對數據結構進行修改。但是實際使用中索引的結構和配置難免變化,那麼只要對下一個間隔的索引進行修改,原來的索引維持原狀。這樣就有了一定的靈活性。
要想實現這個效果,我們只需要在需要變化的索引那天將模板重新建立即可。
- 查詢範圍優化
因爲一般情況並不會查詢全部時間週期的數據,那麼通過切分索引,物理上減少了掃描數據的範圍,也是對性能的優化。
注意
使用索引模板,一般在向索引中插入第一條數據創建索引,如果ES中的Shard特別多,有可能創建索引會變慢,如果延遲不能接受,可以不使用模板,使用定時腳本在頭一天提前建立第二天的索引。
Idea中操作ElasticSearch
(1) 選擇操作ES的java客戶端
目前市面上有兩類客戶端
- 一類是TransportClient 爲代表的ES原生客戶端,不能執行原生DSL語句必須使用它的Java api方法。
- 一類是以Rest ApI爲主的client,最典型的就是jest。 這種客戶端可以直接使用DSL語句拼成的字符串,直接傳給服務端,然後返回json字符串再解析。
兩種方式各有優劣,但是最近ElasticSearch官網,宣佈計劃在7.0以後的版本中廢除TransportClient,以RestClient爲主。
所以在官方的RestClient 基礎上,進行了簡單包裝的Jest客戶端,就成了首選,而且該客戶端也與SpringBoot完美集成。
Jest相關依賴
<dependencies> <!--Java操作ES的客戶端工具Jest--> <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>5.3.3</version> </dependency> <!--Jest需要的依賴--> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>4.5.2</version> </dependency> <!--Jest需要的依賴--> <dependency> <groupId>org.codehaus.janino</groupId> <artifactId>commons-compiler</artifactId> <version>3.0.16</version> </dependency> <!-- ElasticSearch依賴 --> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.6.0</version> </dependency> </dependencies>