前言
我們從之前的一堆鋪墊中,也對elasticsearch有了基本了解。
為了理解elasticsearch是如何組織數據的,我們可以從以下兩個方面來觀察:
- 邏輯設計,我們可以把elasticsearch與關系型數據做個客觀對比:
Relational DB | Elasticsearch |
---|---|
數據庫(database) | 索引(indices) |
表(tables) | types |
行(rows) | documents |
字段(columns) | fields |
elasticsearch(集群)中可以包含多個索引(數據庫),每個索引中可以包含多個類型(表),每個類型下又包含多個文檔(行),每個文檔中又包含多個字段(列)。
- 物理設計,在elasticsearch后台是如何處理這些數據的呢?elasticsearch將每個索引划分為多個分片,每份分片又可以在集群中的不同服務器間遷移。
注意:當然,這里需要補充的是,從elasticsearch
的第一個版本開始,每個文檔都存儲在一個索引中,並分配多個映射類型,映射類型用於表示被索引的文檔或者實體的類型,但這也帶來了一些問題(詳情參見Removal of mapping types),導致后來在elasticsearch6.0.0
版本中一個文檔只能包含一個映射類型,而在7.0.0
中,映射類型則將被棄用,到了8.0.0
中則將完全被刪除。
我們先從邏輯設計開始,即從程序視角開始。
邏輯設計:文檔、類型、索引
一個索引類型中,包含多個文檔,比如說文檔1,文檔2。
當我們索引一篇文檔時,可以通過這樣的順序找到它:索引
▷類型
▷文檔ID
,通過這個組合我們就能索引到某個具體的文檔。
注意:ID不必是整數,實際上它是個字符串。
文檔
之前說elasticsearch是面向文檔的,那么就意味着索引和搜索數據的最小單位是文檔,elasticsearch中,文檔有幾個重要屬性:
- 自我包含,一篇文檔同時包含字段和對應的值,也就是同時包含
key:value
- 可以是層次型的,一個文檔中包含自文檔,復雜的邏輯實體就是這么來的
- 靈活的結構,文檔不依賴預先定義的模式,我們知道關系型數據庫中,要提前定義字段才能使用,在elasticsearch中,對於字段是非常靈活的,有時候,我們可以忽略該字段,或者動態的添加一個新的字段。
- 文檔是無模式的,也就是說,字段對應值的類型可以是不限類型的。
盡管我們可以隨意的新增或者忽略某個字段,但是,每個字段的類型非常重要,比如一個年齡字段類型,可以是字符串也可以是整型。因為elasticsearch會保存字段和類型之間的映射及其他的設置。這種映射具體到每個映射的每種類型(因此帶來的問題),這也是為什么在elasticsearch中,類型有時候也稱為映射類型。
類型
類型是文檔的邏輯容器,就像關系型數據庫一樣,表格是行的容器。
類型中對於字段的定義稱為映射,比如name
映射為字符串類型。
我們說文檔是無模式的,它們不需要擁有映射中所定義的所有字段,比如新增一個字段,那么elasticsearch是怎么做的呢?elasticsearch會自動的將新字段加入映射,但是這個字段的不確定它是什么類型,elasticsearch就開始猜,如果這個值是18,那么elasticsearch會認為它是整型。
但是elasticsearch也可能猜不對,所以最安全的方式就是提前定義好所需要的映射,這點跟關系型數據庫殊途同歸了,先定義好字段,然后再使用,別整什么幺蛾子。后面在討論更多關於映射的東西。
索引
索引是映射類型的容器,elasticsearch中的索引是一個非常大的文檔集合。索引存儲了映射類型的字段和其他設置。然后它們被存儲到了各個分片上了。
我們來研究下分片是如何工作的。
物理設計:節點和分片
一個集群包含至少一個節點,而一個節點就是一個elasticsearch進程。節點內可以有多個索引。
默認的,如果你創建一個索引,那么這個索引將會有5個分片(primary shard,又稱主分片)構成,而每個分片又有一個副本(replica shard,又稱復制分片),這樣,就有了10個分片。
那么這個索引是如何存儲在集群中的呢?
上圖是一個有3個節點的集群,可以看到主分片和對應的復制分片都不會在同一個節點內,這樣有利於某個節點掛掉了,數據也不至於丟失。
實際上,一個分片是一個Lucene索引,一個包含倒排索引的文件目錄,倒排索引的結構使得elasticsearch在不掃描全部文檔的情況下,就能告訴你哪些文檔包含特定的關鍵字。
不過,等等,倒排索引是什么鬼?
倒排索引
elasticsearch使用的是一種稱為倒排索引的結構,采用Lucene倒排索作為底層。這種結構適用於快速的全文搜索,一個索引由文檔中所有不重復的列表構成,對於每一個詞,都有一個包含它的文檔列表。
倒排列表(Posting List)記錄了詞條對應的文檔集合,由倒排索引項(Posting)組成。
倒排索引項主要包含如下信息:
- 文檔id,用於獲取原始信息。
- 詞條頻率(TF,Term Frequency),記錄該詞條在文檔中出現的次數,用於后續相關性算分。
- 位置(Position),記錄詞條在文檔中的分詞位置(多個),用於做短語搜索(Phrase Query)。
- 偏移(Offset),記錄詞條在文檔的開始和結束位置,用於做高亮顯示。
以搜索引擎
為例:
文檔id | 文檔內容 |
---|---|
1 | elasticsearch是最流行的搜索引擎 |
2 | Python是世界上最好的語言 |
3 | 搜索引擎是如何誕生的 |
上述文檔的倒排索引列表是這樣的:
DocID | TF | Position | Offset |
---|---|---|---|
1 | 1 | 2 | <18,22> |
3 | 1 | 0 | <0,4> |
關於文檔1,DocID
是1無需多說,TF
是1表示搜索引擎
在文檔內容中出現一次,Position
指的是分詞后的位置,首先要說文檔內容會被分為elasticsearch
、最流行
、搜索引擎
3部分,從0開始計算,搜索引擎
的Position
是2;Offset
是搜索引擎
這個字符在文檔中的位置。
文檔3中搜索引擎
在文檔中出現一次(TF:1),並且出現在文檔的開始位置(Position:0),那么Offset
的位置就是<0,4>
無疑了。
再比如說,現在有兩個文檔, 每個文檔包含如下內容:
Study every day, good good up to forever # 文檔1包含的內容
To forever, study every day, good good up # 文檔2包含的內容
為了創建倒排索引,我們首先要將每個文檔拆分成獨立的詞(或稱為詞條或者tokens),然后創建一個包含所有不重復的詞條的排序列表,然后列出每個詞條出現在哪個文檔:
term | doc_1 | doc_2 |
---|---|---|
Study | √ | × |
To | × | √ |
every | √ | √ |
forever | √ | √ |
day | √ | √ |
study | × | √ |
good | √ | √ |
every | √ | √ |
to | √ | × |
up | √ | √ |
現在,我們試圖搜索to forever
,只需要查看包含每個詞條的文檔:
term | doc_1 | doc_2 |
---|---|---|
to | √ | × |
forever | √ | √ |
total | 2 | 1 |
兩個文檔都匹配,但是第一個文檔比第二個匹配程度更高。如果沒有別的條件,現在,這兩個包含關鍵字的文檔都將返回。
再來看一個示例,比如我們通過博客標簽來搜索博客文章。那么倒排索引列表就是這樣的一個結構:
如果要搜索含有python
標簽的文章,那相對於查找所有原始數據而言,查找倒排索引后的數據將會快的多。只需要查看標簽這一欄,然后獲取相關的文章ID即可。
elasticsearch的索引和Lucene的索引對比
在elasticsearch中,索引
這個詞被頻繁使用,這就是術語的使用。
並且elasticsearch將索引被分為多個分片,每份分片是一個Lucene的索引。所以一個elasticsearch索引是由多個Lucene索引組成的。別問為什么,誰讓elasticsearch使用Lucene作為底層呢!
如無特指,說起索引都是指elasticsearch的索引。
see also: [倒排索引](https://www.elastic.co/guide/cn/elasticsearch/guide/current/inverted-index.html) | [倒排索引原理和實現](https://blog.csdn.net/u011239443/article/details/60604017) 歡迎斧正,that's all