官术网_书友最值得收藏!

2.1 全文檢索與倒排索引

在許多文獻中,Elasticsearch被歸類為NoSQL數據庫,所以它更多地具備一些NoSQL數據庫特征,而與傳統(tǒng)關系型數據庫完全不同。比如在Elasticsearch中索引的概念與傳統(tǒng)關系型數據庫中的索引就不盡相同,Elasticsearch中的索引是倒排索引(Inverted Index),是一種專門應用于全文檢索的索引類型,與此類似的還有映射、類型、文檔、字段等諸多概念。在一些文獻中常使用傳統(tǒng)關系型數據庫中的庫、表、行、列來類比Elasticsearch的這些概念,盡管這種類比并不正確,但對于初學者來說也是快速理解這些概念的一種途徑。所以本節(jié)也先以這種方式整體解釋一下這些概念,隨著學習的逐步深入會慢慢給出正確的理解:

● 索引(Index)相當于庫;

● 映射類型(Mapping Type)相當于表;

● 文檔(Document)相當于行;

● 字段(Field)相當于列。

2.1.1 全文檢索

先來解釋一下什么叫全文檢索。數據檢索的目的是從一系列數據中,根據某一或某些數據特征將特定的數據找出來。從數據檢索的角度來看,數據大體上可以分為兩種類型:一種是結構化數據;另一種是非結構化數據。結構化數據將數據具有的特征事先以結構化的形式定義好,數據有固定的格式或有限的長度。典型的結構化數據就是傳統(tǒng)關系型數據庫的表結構,數據特征直接體現在表結構的字段上,所以根據某一特征做數據檢索很直接,速度也比較快。比如,根據商品的名稱將該商品全部查找出來,通過一條SQL語句就能實現。如果想要提高查找速度,只要在商品名稱上創(chuàng)建索引就可以了。許多應用系統(tǒng)都是建立在結構化數據的基礎之上,例如財務軟件、CRM、MIS等。

非結構化數據則完全不同,它們沒有預先定義好的結構化特征,也沒有固定格式和固定長度。典型的非結構化數據包括文章、圖片、視頻、網頁、郵件等,其中像HTML網頁這種具有一定格式的文檔也稱為半結構化數據。顯而易見,相比結構化數據,非結構化數據的檢索要難得多。在對它們的檢索中,像文章、網頁、郵件這種全文本(Full-text)數據的檢索需求占了大多數,而且與圖片、視頻等非文本數據的檢索完全不同,因此形成了一門獨立的學科,這就是全文檢索。包括Elastic官方網站在內的很多文獻中,經常稱全文本數據為全文數據,稱全文數據中的一條數據為文檔(Document),而稱存儲全文數據的數據庫為全文數據庫。本書后續(xù)章節(jié)提到的文檔,如果沒有特別說明,都是指存儲在全文數據庫的一條全文數據。所以簡單來說,全文檢索是指在全文數據中檢索單個文檔或文檔集合的搜索技術,而Elasticsearch從這個意義上來說也可以理解為是一個全文數據庫。

與結構化查詢相比,全文檢索面臨的最大問題就是性能問題。全文檢索最一般的應用場景是根據一些關鍵字查找包含這些關鍵字的文檔,比如互聯網搜索引擎要實現的功能就是根據一些關鍵字查找網頁。顯然,如果沒有對文檔做特別處理,查找的辦法似乎只能是逐條比對。具體來說就是先將所有文檔都讀取出來,再對文檔內容做逐行掃描看是否包含這些關鍵字。例如,Linux中的grep命令就是通過這種算法實現的。但這種方法在數據量非常大的情況下就像海底撈針一樣,速度一定會非常慢。而類似互聯網搜索引擎這樣的應用面對的文檔數量往往都是天文數字,所以需要有一種更好的辦法實現全文檢索。

關系型數據庫提升數據查詢速度的常用方法是給字段添加索引,有了索引的字段會根據字段值排序并創(chuàng)建類似排序二叉樹的數據結構(如B樹),這樣就可以利用二分查找等算法提升查詢速度。所以在字段添加索引后,通過這些字段做查詢時速度能夠得到非常明顯的提升。但由于添加索引后需要對字段排序,所以增加和刪除數據時速度會變慢,并且還需要額外的空間存儲索引。這是典型的利用空間換取時間的策略。普通的索引對全文檢索并不適用,因為這種索引使用字段整體值參與排序,所以在檢索時也要通過字段的整體值做查詢條件。而全文檢索一般是查詢包含某一或某些關鍵字的文檔,所以通過文檔整體值建立的索引對提高查詢速度是沒有任何幫助的。為了解決這個問題,人們創(chuàng)建了一種新索引方法,這種索引方法就是倒排索引(Inverted Index)。

2.1.2 倒排索引

倒排索引先將文檔中包含的關鍵字全部提取出來,然后再將關鍵字與文檔的對應關系保存起來,最后再對關鍵字本身做索引排序。用戶在檢索某一關鍵字時,可以先對關鍵字的索引進行查找,再通過關鍵字與文檔的對應關系找到所在文檔。這類似于查字典一樣,字典的拼音表和部首表就是關鍵字索引,而拼音表和部首表中的內容就是關鍵字與文檔的對應關系。為了說明倒排索引的基本思想,以下面兩條文檔為例:

示例2-1 參與倒排索引的文檔

針對這兩份文檔創(chuàng)建倒排索引的第一步,是先對文檔提取關鍵字。對于英文來說比較簡單按空格分隔即可,兩份文檔共提取I、love、elasticsearch和logstash四個關鍵字。接下來就是建立關鍵字與文檔之間的對應關系,即標識關鍵字都被哪些文檔包含。這里使用表2-1所示的形式來表示這種對應關系,“√”代表文檔包含了該關鍵字:

表2-1 倒排索引基本結構

有了倒排索引,用戶檢索就可以在倒排索引中快速定位到包含關鍵字的文檔。倒排索引與關系型數據庫索引類似,會根據關鍵字做排序。但關系型數據庫索引一般是對主鍵創(chuàng)建,然后索引指向數據內容;而倒排索引則正好相反,它是針對文檔內容創(chuàng)建索引,然后索引指向主鍵(文檔一、文檔二),這就是這種索引被稱為倒排索引的原因。

從以上分析可以看出,倒排索引實際上是對全文數據結構化的過程。對于存儲在關系型數據庫中的數據來說,它們依賴于人的預先分析將數據拆解為不同字段,所以在數據插入時就已經是結構化的;而在全文數據庫中,文檔在插入時還不是結構化的,需要應用程序根據規(guī)則自動提取關鍵字,并形成關鍵字與文檔之間的結構化對應關系。由于文檔在創(chuàng)建時需要提取關鍵字并創(chuàng)建索引,所以向全文數據庫添加文檔比關系型數據庫要慢一些。

不難看出,全文檢索中提取關鍵字是非常重要的一步。這些預先提取出來的關鍵字,在Elasticsearch及全文檢索的相關文獻中一般稱為詞項(Term),本書后續(xù)章節(jié)將不再使用關鍵字而改用詞項這個專業(yè)術語。文檔的詞項提取在Elasticsearch中稱為文檔分析(Analysis),是整個全文檢索中較為核心的過程。這個過程必須要區(qū)分哪些是詞項,哪些不是。對于英文來說,它還必須要知道apple和apples指的同一個東西,而run和running指的是同一動作。對于中文來說就更麻煩了,因為中文詞語不以空格分隔,所以面臨的第一難題是如何將詞語分辨出來。文檔分析涉及的內容很多,將在本書第4章詳細講解。

2.1.3 Elasticsearch索引

在Elasticsearch中,添加或更新文檔時最重要的動作是將它們編入倒排索引,未被編入倒排索引的文檔將不能被檢索。也就是說,Elasticsearch中所有數據的檢索都必須要通過倒排索引來檢索,離開了倒排索引文檔就相當于不存在。所以從檢索的角度來看,文檔以倒排索引的形式表現其存在性。正是基于這個原因,Elasticsearch沒有引入庫的概念,而是將文檔的容器直接稱為索引(Index)。而這里的索引就是倒排索引,或者更準確的說是一組倒排索引。在概念上可以將索引理解為文檔在物理上的區(qū)分,同一索引中的文檔具有相同的索引策略,或者說它們被編入到同一組索引中。從檢索的角度來說,用戶在檢索文檔時也要指定從哪一個索引中檢索文檔。所以從存儲和檢索兩個角度來看,以索引區(qū)分文檔實在是再合適不過了。在Elasticsearch中存儲文檔最好預先創(chuàng)建索引,盡管這并不是必須的。用戶預先創(chuàng)建索引可以指明文檔存儲時怎么分詞,如何創(chuàng)建索引等重要配置信息,這些對于提升檢索速度顯然是有益的。

因為文檔存儲前的分析和索引過程比較耗資源,所以為了提升性能,文檔在添加到Elasticsearch中時并不會立即被編入索引。在默認情況下,Elasticsearch會每隔1s統(tǒng)一處理一次新加入的文檔,可以通過index.refresh_interval參數修改。為了提升性能,在Elasticsearch 7中還添加了index.search.idle.after參數,它的默認值是30s。其大體含義是,如果索引在一段時間內沒有收到檢索數據的請求,那么它至少要等30s后才會刷新索引數據。所以,從這兩個參數的作用來看,Elasticsearch實際上是準實時的(Near Realtime,NRT)。也就是說,新添加到索引中的文檔,有可能在一段時間內不能被檢索到。如果的確需要立即檢索到文檔,Elasticsearch也提供了強制刷新到索引的方式,包括使用_refresh接口和在操作文檔時使用refresh參數。但這會對性能造成一定的影響,詳細請參見第3章。

那么未被編入索引的文檔在什么地方呢?事實上,它們會被臨時保存到緩沖區(qū)中,緩沖區(qū)的大小可以通過一些配置參數設置,包括indices.memory.index_buffer_size、indices.memory.min_index_buffer_size和indices.memory.max_index_buffer_size。默認情況下,這個緩沖區(qū)最小為48MB且沒有上限。

2.1.4 Elasticsearch映射

如前文所述,索引是存儲文檔的容器,文檔在存儲前會做文檔分析并編入倒排索引。而文檔從全文數據到索引的轉變由映射(Mapping)定義,這是另一個在Elasticsearch中非常重要的概念。映射介于文檔與索引之間,所以一般是在創(chuàng)建索引時指定文檔與索引的映射關系。映射的概念比較難理解,想要理解它就得先理解Elasticsearch中的文檔概念。

1.文檔

在Elasticsearch中,數據存儲和檢索的基本單元是文檔。Elasticsearch的文檔使用JSON格式,這種格式目前幾乎已經成為互聯網數據交換的標準格式。Elasticsearch對外開放的接口以REST為主,而REST本身也是以JSON為通用數據交換格式。在后續(xù)章節(jié)中會看到,無論是存儲文檔、檢索文檔還是設置索引,請求的基本格式都是JSON。所以從開發(fā)和應用的角度來看,JSON格式可以降低學習成本,而且與微服務架構也易于集成。熟悉JSON的讀者應該知道,JSON有一些格式規(guī)范要求,比如屬性名稱、數據類型等。所以嚴格來說,Elasticsearch存儲的文檔是一種半結構化數據,可以預先定義好屬性和數據類型。為了明確概念,本書后續(xù)章節(jié)稱Elasticsearch中文檔的JSON屬性為字段(Field),即文檔字段,以區(qū)別在其他領域中使用的JSON屬性。

既然Elasticsearch支持全文檢索,為什么還要預先定義文檔字段和數據類型呢?這可以從以下幾個方面理解。首先,全文數據在存儲前需要做分析并提取詞項,但在文檔中并不是所有數據都需要這樣做。比如文檔創(chuàng)建時間、文章標題、作者等,這些數據本來就是結構化的,沒有必要再分析。此外,一些結構化數據在檢索時需要做精確匹配,如果做了文檔分析并提取詞項后,反而做不了精確匹配了。比如,對作者名稱“tom smith”做文檔分析后,會提取“tom”和“smith”兩個詞項編入索引,而“tom smith”則不會編入索引,這時通過“tom smith”檢索文章就不能匹配到文檔了。其次,預先定義好文檔字段可以增加數據檢索的維度,提升檢索質量;而且預先定義好數據類型可以優(yōu)化存儲結構,比如數值類型的保存就沒必要保存成字符串了。最后,在Elasticsearch中存儲文檔也不是一定要預先定義文檔字段,Elasticsearch也支持動態(tài)映射文檔字段。

所以在使用Elasticsearch時,如果清楚地知道文檔存在一些結構化特征,預先定義好它們對存儲和檢索都有好處;而這種預先定義又不會像數據庫表結構那樣,限制未來可能出現的數據擴展,可以說是兼顧了效率與靈活。在Elasticsearch中,定義文檔的字段和數據類型是通過在映射中定義類型來實現的。

2.映射類型

映射類型(Mapping Type)是定義文檔與索引映射關系的一種方式。在Elasticsearch版本6之前,一個索引中是可以定義多個映射類型。例如,創(chuàng)建一個shop索引存儲網上商城數據,可以包含用戶類型users和商品類型products。每個類型都可以有自己的字段,因此users類型可以有name、age、address等字段,而products類型則可以有name、price、description等字段。每新增一個用戶,可以以JSON文檔的形式存儲在users類型下;而每新增一個商品,同樣也可以以JSON文檔的形式存儲在products類型下,如示例2-2所示:

示例2-2 創(chuàng)建映射類型

在示例2-2中,“PUT shop”是創(chuàng)建索引的REST請求,而請求體中的mappings參數就是文檔到索引的映射關系,它是索引創(chuàng)建接口的一個基本配置參數。mappings中的users和products就是映射類型的名稱,而在映射類型的properties參數中,則實際指明了這些映射類型中預定義的字段及其數據類型。

講到這里再回頭看一下本節(jié)開始時對它們的類比,就會發(fā)現這種與關系型數據庫的類比并不正確。最主要的就是映射類型并不是文檔的物理容器,而只是文檔到索引轉變的映射關系。事實上,映射類型這個概念的引入使得Elasticsearch的這些概念在整體上都變得混亂,尤其是在它的官方文獻中還經常將映射類型簡稱為類型,這使得初學者更是一頭霧水。不光是初學者覺得這些概念難理解,Elasticsearch官方也應該是感覺到這些概念有些混亂了,所以Elasticsearch官方已經開始弱化映射類型的概念。例如,上面這段代碼如果放到第1章搭建的Kibana中去執(zhí)行就會報錯,如圖2-1所示。

圖2-1 創(chuàng)建多映射類型

是否可以成功執(zhí)行,取決于使用Elasticsearch的版本,只有使用6.0之前的版本可以成功。事實上,Elasticsearch官方正計劃逐步取消映射類型的概念,在6.0版本以后映射類型的概念還將延續(xù),但在映射中只能有一個映射類型,而不允許再定義多個映射類型;而在7.0版本以后,映射類型的概念被徹底刪除。所以類似示例2-2中在shop索引中創(chuàng)建多個映射類型的例子,可以將shop這一層的索引取消,直接建立users和products索引,通過索引對它們做邏輯上和物理上的隔離。在6.0—7.0版本的過渡期間,用戶在創(chuàng)建索引時還是需要在索引下創(chuàng)建一個映射類型,映射類型名稱可以任意定義,但一般可以起名為_doc。但在7.0以后的版本中則不需要再加映射類型,Elasticsearch會為索引創(chuàng)建惟一一種映射類型_doc。所以,在7.0以后的版本中,如果要創(chuàng)建示例2-2中的索引應該按如下方式執(zhí)行:

示例2-3 新接口中沒有映射類型

需要說明的是,Elasticsearch官方之所以要刪除映射類型的概念,不單純是因為映射類型容易造成混亂,主要是因為映射類型只是文檔在邏輯上的容器,在物理上并沒有起到隔離文檔的作用。在同一索引中,不同映射類型中具有相同名稱的字段實際上由相同的Lucene字段支持。以前面shop索引為例,users類型中的name字段將與products類型中的name字段共享同一字段,所以兩個name字段必須具有相同的定義。這在一些情況下或許是合理的,比如兩種映射類型存在類似父子繼承關系;但在多數情況下,這種共享字段會引發(fā)歧義。所以,使用表結構類比映射類型是不合適的,因為表結構在物理上是隔離的。同一數據庫的兩個表結構如果擁有相同字段,它們相互之間不會受到任何影響。但在Elasticsearch中,這是不成立的。正是基于這樣的原因,Elasticsearch在高版本中開始弱化映射類型這一概念,未來定義不同映射類型就是創(chuàng)建不同的索引。

主站蜘蛛池模板: 芦山县| 南汇区| 库车县| 休宁县| 湘潭县| 陕西省| 锡林郭勒盟| 泸水县| 大同市| 呼和浩特市| 衡南县| 灵宝市| 中方县| 扶余县| 勃利县| 南康市| 建德市| 平顺县| 宜川县| 屯昌县| 吴旗县| 郧西县| 平阳县| 文安县| 凉山| 叶城县| 玉林市| 新建县| 姜堰市| 乃东县| 台南县| 博湖县| 宜丰县| 巴彦县| 左贡县| 湘潭县| 华亭县| 凉城县| 修武县| 汉中市| 津南区|