本文從基本概念、基本CRUD操作、倒排索引原理、分詞等部分來初識Elasticsearch。
2.1 基本概念
Elasticsearch是面向文件(Document)的,文件是所有可搜尋資料的最小單位;文件會被序列化成Json格式,儲存在Elasticsearch中,並且每個文件都有一個唯一ID,可以通過Elasticsearch自動生成,也可以自己進行指定。對比MySQL,每行資料都有一個主鍵,這個主鍵可以使用MySQL自增主鍵,也可以通過雪花演算法等方式生成然後進行自己設定。
文件的後設資料,用於標註文件的相關資訊。例如:_index表示文件所屬的索引名,_id表示文件唯一ID,_score表示相關性打分,_source是文件的原始Json資料等。
索引(Index)是文件的容器,是一類文件的集合。對比MySQL,可以認為索引為一個資料表。
Mapping用來定義欄位名和型別,對比MySQL,每個表有表結構的定義,包括欄位名稱,欄位型別等。與關係型資料庫進行類比:
RDBMS | Elasticsearch |
---|---|
Table | Index |
Row | Document |
Column | Field |
Schema | Mapping |
SQL | DSL |
節點是一個Elasticsearch的例項,本質上是一個Java程式。一臺機器上可以執行多個Elasticsearch程式,但是生產環境一般建議一臺機器上值執行一個Elasticsearch例項。節點分為資料節點和協調節點。資料節點是儲存資料的節點,協調節點負責接收Client的請求,將請求路由到到合適的節點,並將結果彙集到一起。
叢集是有多個節點組成的。
分片分為主分片和副本,每個分片可以設定一定數量的副本。主分片用於解決資料的水平擴充套件問題,通過主分片可以將資料分佈到叢集內的所有節點上。副本是用來解決資料高可用的問題,副本是主分片的拷貝,副本分片數可以動態調整,增加副本數,可以在一定程度上提高服務的可用性。當然副本可以提供查詢功能,分攤系統的讀負載。例如下圖中,分片數為3,副本數為1。
對於分片的設定,生產環境中分片的設定需要提前進行規劃。分片數量設定過小會導致後續無法增加節點實現水平擴充套件;而單個分片資料量太大,會導致資料重新分片耗時。分片數設定過大,會影響搜尋結果的相關性打分,影響統計結果的準確性;而單個節點上有過多的分片,會導致資源浪費,同時會影響效能。
2.2 基本CRUD操作與批量操作
Elasticsearch對外提供RESTful API用於CRUD。使用RESTful API與Elasticsearch進行互動有兩種方式:curl命令列和Kibana DevTools。可以直接使用Kibana DevTool與Elasticsearch進行互動。
2.2.1 索引操作
- 建立索引
request:PUT /test_index
response:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "test_index"
}
-
檢視現有索引
request:GET _cat/indices
response:green open test_index GRXXECvrQjuNKRog7aDkPQ 1 1 2 3 28.9kb 14.4kb
-
刪除索引
request:DELETE /test_index
response:
{
"acknowledged" : true
}
2.2.2 文件操作
- 指定id建立文件
request:
PUT /test_index/_doc/1
{
"username":"Paul",
"age":10
}
response:
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
- 不指定id建立文件
request:
POST /test_index/_doc
{
"username":"Rose",
"age":11
}
response:
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "soOv1HcB4Isa6tvVdQ9J",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
- 指定id查詢文件
request:GET /test_index/_doc/1
response:
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"username" : "Paul",
"age" : 10
}
}
- 查詢所有文件
request:GET /test_index/_search
response:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"username" : "Paul",
"age" : 10
}
},
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "soOv1HcB4Isa6tvVdQ9J",
"_score" : 1.0,
"_source" : {
"username" : "Rose",
"age" : 11
}
}
]
}
}
- 更新文件
request:
POST /test_index/_update/1
{
"doc": {
"username": "Paul",
"age": 20
}
}
response:
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 2,
"_primary_term" : 1
}
- 刪除文件
request:DELETE /test_index/_doc1
2.2.3 批量操作
批量操作可以減少網路連線所產生的開銷,提高效能。
- _bulk
支援在一次API呼叫中,對不同的索引進行操作。
bulk支援Index、Create、Update?Delete四種操作。
請求中單條操作失敗,並不會影響其他操作,返回結果中包含每一條操作的執行結果。
request:
POST _bulk
{"index":{"_index":"test_index", "_id":"1"}}
{"username":"Smart", "age":22}
{"delete":{"_index":"test_index", "_id":"2"}}
response:
{
"took" : 95,
"errors" : false,
"items" : [
{
"index" : {
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 5,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 5,
"_primary_term" : 1,
"status" : 200
}
},
{
"delete" : {
"_index" : "test_index",
"_type" : "_doc",
"_id" : "2",
"_version" : 1,
"result" : "not_found",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 6,
"_primary_term" : 1,
"status" : 404
}
}
]
}
- 批量讀取mget
request:
GET _mget
{
"docs":[
{
"_index":"test_index",
"_id":1
},
{
"_index":"movies",
"_id":1
}]
}
response:
{
"docs" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "1",
"_version" : 2,
"_seq_no" : 2,
"_primary_term" : 1,
"found" : true,
"_source" : {
"username" : "Paul",
"age" : 20
}
},
{
"_index" : "movies",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"_seq_no" : 6,
"_primary_term" : 1,
"found" : true,
"_source" : {
"id" : "1",
"title" : "Toy Story",
"year" : 1995,
"genre" : [
"Adventure",
"Animation",
"Children",
"Comedy",
"Fantasy"
],
"@version" : "1"
}
}
]
}
- 批量查詢msearch
request:
POST test_index/_msearch
{}
{"query":{"match_all":{}},"size":1}
{"index":"kibana_sample_data_flights"}
{"query":{"match_all":{}},"size":2}
response:
{
"took" : 4,
"responses" : [
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "soOv1HcB4Isa6tvVdQ9J",
"_score" : 1.0,
"_source" : {
"username" : "Rose",
"age" : 11
}
}
]
},
"status" : 200
},
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 10000,
"relation" : "gte"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "kibana_sample_data_flights",
"_type" : "_doc",
"_id" : "iTmvUXcBNxIYppLoFWwg",
"_score" : 1.0,
"_source" : {
"FlightNum" : "R3J7URU",
"DestCountry" : "US",
"OriginWeather" : "Hail",
"OriginCityName" : "Moscow",
"AvgTicketPrice" : 1172.5681640799792,
"DistanceMiles" : 5149.888524287689,
"FlightDelay" : false,
"DestWeather" : "Rain",
"Dest" : "Spokane International Airport",
"FlightDelayType" : "No Delay",
"OriginCountry" : "RU",
"dayOfWeek" : 6,
"DistanceKilometers" : 8287.942197231247,
"timestamp" : "2021-02-14T10:59:03",
"DestLocation" : {
"lat" : "47.61989975",
"lon" : "-117.5339966"
},
"DestAirportID" : "GEG",
"Carrier" : "ES-Air",
"Cancelled" : false,
"FlightTimeMin" : 753.4492906573861,
"Origin" : "Sheremetyevo International Airport",
"OriginLocation" : {
"lat" : "55.972599",
"lon" : "37.4146"
},
"DestRegion" : "US-WA",
"OriginAirportID" : "SVO",
"OriginRegion" : "RU-MOS",
"DestCityName" : "Spokane",
"FlightTimeHour" : 12.557488177623101,
"FlightDelayMin" : 0
}
},
{
"_index" : "kibana_sample_data_flights",
"_type" : "_doc",
"_id" : "ijmvUXcBNxIYppLoFWwg",
"_score" : 1.0,
"_source" : {
"FlightNum" : "OE9TTXI",
"DestCountry" : "GB",
"OriginWeather" : "Sunny",
"OriginCityName" : "Guangzhou",
"AvgTicketPrice" : 834.6361636829536,
"DistanceMiles" : 5911.063226254684,
"FlightDelay" : false,
"DestWeather" : "Thunder & Lightning",
"Dest" : "London Heathrow Airport",
"FlightDelayType" : "No Delay",
"OriginCountry" : "CN",
"dayOfWeek" : 6,
"DistanceKilometers" : 9512.93413679362,
"timestamp" : "2021-02-14T08:13:00",
"DestLocation" : {
"lat" : "51.4706",
"lon" : "-0.461941"
},
"DestAirportID" : "LHR",
"Carrier" : "JetBeats",
"Cancelled" : true,
"FlightTimeMin" : 500.68074404176946,
"Origin" : "Guangzhou Baiyun International Airport",
"OriginLocation" : {
"lat" : "23.39240074",
"lon" : "113.2990036"
},
"DestRegion" : "GB-ENG",
"OriginAirportID" : "CAN",
"OriginRegion" : "SE-BD",
"DestCityName" : "London",
"FlightTimeHour" : 8.344679067362824,
"FlightDelayMin" : 0
}
}
]
},
"status" : 200
}
]
}
2.3 倒排索引
2.3.1 正排索引與倒排索引
什麼是正排索引?正排索引指的是從文件Id到文件內容、單詞的關聯關係。例如每本書的目錄,通過目錄可以很快找到某個標題的具體內容在書中的那一頁。
什麼是倒排索引?倒排索引指的文件內容或者單詞到文件Id的關聯關係。還是以書的例子,倒排索引指的是從具體內容到文章標題的索引。
知乎上面有人舉了一個形象的例子。比如說考我們一首詩,給一首詩的名字,通常大家都可以背下來詩的內容。那為什麼“飛花令”的時候我們想不起來詩句呢?因為我們的大腦中沒有建立從詩句中某個字到詩名的倒排索引,假如說建立了這樣的倒排索引,我們也可以像中國詩詞大會的選手一樣飛來飛去。
文件ID | 文件內容 |
---|---|
1 | Elasticsearch是最流行的搜尋引擎 |
2 | Java是世界上最好的語言 |
3 | Google是全球最大的搜尋引擎 |
單詞 | 文件ID列表 |
---|---|
elasticsearch | 1 |
流行 | 1 |
搜尋引擎 | 1,3 |
java | 2 |
世界 | 2 |
最好 | 2 |
語言 | 2 |
3 | |
全球 | 3 |
最大 | 3 |
Elasticsearch儲存的是一個json格式的文件,其中包含多個欄位,每個欄位都會有自己的倒排索引。
那倒排索引是如何產生的呢?是文件內容分詞之後和文件ID進行關聯。
2.4 分詞
分詞是指將連續的字串按照一定的規則重新切分成為單詞(term or token)的過程,在ES裡面叫做Analysis。
2.4.1 ES分詞器組成和自帶分詞器
Analyzer是ES中專門處理分詞的元件,組成如下:
- Character Filters:針對原始文字進行處理,比如去除HTML特殊識別符號等
- Tokenizer:將原始文字按照一定規則切分成為單詞
- Token Filters:針對Tokenizer處理的單詞進行再加工,比如轉小寫、刪除停用詞或者新增同義詞等處理
其工作過程如圖所示:
Elasticsearch內建分詞器
分詞器 | 說明 |
---|---|
Standard Analyzer | 預設分詞器,按詞切分,小寫處理,停用詞處理預設關閉 |
Simple Analyzer | 按照非字母切分,非字母的都被去除,小寫處理 |
Stop Analyzer | 小寫處理,停用詞過濾 |
Whitespace Analyzer | 按照空格切分,不轉小寫 |
Keyword Analyzer | 不分詞,直接將輸入內容進行輸出 |
Pattern Analyzer | 正規表示式,預設\W+(非字母符號分割) |
Language | 提供30多種常見語言的分詞器 |
Customer Analyzer | 自定義分詞器 |
測試一下:
- 直接指定Analyzer進行測試
GET /_analyze
{
"analyzer": "standard",
"text": [
"Hello World, Hello Elasticsearch"
]
}
- 指定索引的欄位進行測試
POST /movies/_analyze
{
"field": "title",
"text": [
"Hello World, Hello Elasticsearch"
]
}
- 自定義分詞器進行測試
POST /_analyze
{
"tokenizer": "standard",
"filter": [
"lowercase"
],
"text": [
"Hello World, Hello Elasticsearch"
]
}
2.4.2 Analyze API使用
ES提供一個測試分詞的API介面,方便驗證分詞效果。_analyze
可以直接指定analyzer進行測試
可以直接指定索引中的欄位進行測試:GET test_index/_analyze
可以自定義分詞器進行測試
至此,學習了基本API的使用、批量操作、倒排索引原理和分詞等概念,對ELasticsearch有了初步的認識。