ElasticSearch 的概念解析與使用方式
ElasticSearch(后續(xù)簡(jiǎn)稱 ES)在企業(yè)中的使用可以說(shuō)是非常廣泛了,那么 ES 到底是什么呢?我們學(xué)習(xí) ES 能做到哪些事情呢?接下來(lái)我將用幾篇文章詳細(xì)聊一聊 ES。
ES 是一款高性能的分布式搜索引擎,當(dāng)然里面出現(xiàn)的高性能、分布式已經(jīng)是見怪不怪了,因此我們的重點(diǎn)是在搜索引擎上面。提到搜索引擎肯定不陌生,像百度、谷歌,它們都提供了自己的搜索引擎,我們每天都會(huì)在上面查找各種各樣的信息。
因此:通過輸入指定的關(guān)鍵字(關(guān)鍵詞)來(lái)獲取與之相關(guān)的信息,這個(gè)過程稱之為搜索。并且搜索是不分場(chǎng)合的,除了百度、谷歌提供的搜索引擎之外,我們還可以在各種 app 上搜索,比如你在京東 app 上輸入小提琴,那么點(diǎn)擊確認(rèn)之后會(huì)給你返回與小提琴有關(guān)的商品信息,這也是搜索。
而支持搜索的工具便是搜索引擎,它負(fù)責(zé)根據(jù)用戶輸入的關(guān)鍵字匹配出與之相關(guān)的信息,然后返回給用戶,所以搜索引擎就是支持用戶搜索的一個(gè)工具。
那么都有哪些工具支持搜索呢,其實(shí)說(shuō)白了只要是支持字符串匹配的都可以,但能否滿足不同的業(yè)務(wù)場(chǎng)景、以及保證高級(jí)別的搜索效率就兩說(shuō)了。
使用數(shù)據(jù)庫(kù)做搜索
顯然數(shù)據(jù)庫(kù)是支持搜索的,畢竟它是專門用來(lái)存儲(chǔ)數(shù)據(jù)的,其中也包含了數(shù)據(jù)分析。比如數(shù)據(jù)庫(kù)中有一張表負(fù)責(zé)存儲(chǔ)商品信息,我要查詢里面所有名字包含 "洗發(fā)水" 的商品對(duì)應(yīng)的 id,那么就可以這么做:
SELECT product_id FROM product
WHERE product_name LIKE '%洗發(fā)水%';
很明顯這么做是正確的,但是要拿數(shù)據(jù)庫(kù)來(lái)做搜索引擎則是不合適的。因?yàn)橛捎跇I(yè)務(wù)場(chǎng)景的不同,會(huì)帶來(lái)兩個(gè)問題:
- 假設(shè)一個(gè)商品的名稱不是 "...洗發(fā)水...",而是 "...洗發(fā)液...",這個(gè)時(shí)候該商品就選不到了,但它也是需要被選出來(lái)的;再比如用戶想搜索 "榨汁機(jī)",但是不小心輸成了 "榨汁雞",這個(gè)時(shí)候也沒辦法搜索。所以這種情況下,無(wú)法通過對(duì)關(guān)鍵詞進(jìn)行切分,來(lái)獲取更多的結(jié)果。
- 如果我們不是按商品名稱、而是按商品描述進(jìn)行搜索,那么還會(huì)產(chǎn)生效率上的問題。因?yàn)槟承┳侄蔚膬?nèi)容會(huì)非常長(zhǎng),數(shù)千甚至上萬(wàn)個(gè)字符也是很常見的,這個(gè)時(shí)候要查詢內(nèi)部是否包含關(guān)鍵詞所需要掃描的文本量就會(huì)非常大,并且該字段的每一行記錄都需要掃描。如果一張表里面有千萬(wàn)條記錄,那么這個(gè)耗時(shí)會(huì)非??植馈?/li>
因此用數(shù)據(jù)庫(kù)實(shí)現(xiàn)搜索是不靠譜的,性能會(huì)非常差。
全文檢索和 Lucene
既然數(shù)據(jù)庫(kù)不適合專門用于搜索,那什么工具適合呢?當(dāng)然是我們要聊的 ES。只不過在具體介紹 ES 之前,我們需要先說(shuō)一下什么是全文檢索,以及 Lucene。
首先全文檢索(或者說(shuō)全文搜索)也是一種搜索,只不過它和數(shù)據(jù)庫(kù)中使用 like 不同,全文檢索使用了倒排索引的技術(shù),它分為兩步:
- 索引創(chuàng)建:從數(shù)據(jù)中提取信息,建立倒排索引
- 搜索索引:根據(jù)用戶的查詢?nèi)ニ阉魉饕?,然后返回索引?duì)應(yīng)的結(jié)果
直接說(shuō)的話不容易理解,我們舉例說(shuō)明,假設(shè)數(shù)據(jù)庫(kù)中有一張表 game。
圖片
假設(shè)要搜尋 type 字段包含 "校園" 或者 "愛情" 的記錄,這個(gè)時(shí)候顯然需要全表掃描。如果庫(kù)里面有上千萬(wàn)條記錄,那么就需要掃描上千萬(wàn)次,且每次掃描的范圍都是全部的字符。此外這里指定了多個(gè)關(guān)鍵詞,每個(gè)關(guān)鍵詞都要模糊匹配一遍。
SELECT id, name FROM game
WHERE type LIKE '%校園%' OR type LIKE '%愛情%';
很明顯這種 SQL 在 type 字段比較大的時(shí)候,其性能會(huì)非常差。
因此我們需要建立倒排索引,由于這里要基于 type 字段做查詢,那么就對(duì) type 字段的每一個(gè)文本進(jìn)行拆分,得到多個(gè)關(guān)鍵詞,然后再建立關(guān)鍵詞到 id 的映射。
圖片
通過對(duì)文本進(jìn)行拆分,我們看到 type 字段包含 "親情" 的有 id 為 1、2、3 的記錄,包含 "夏日" 的有 id 為 2、3 的記錄,所以每一個(gè)關(guān)鍵詞都和包含該關(guān)鍵詞的記錄的 id 做了一個(gè)映射。
搜索的時(shí)候,同樣也會(huì)對(duì)關(guān)鍵詞、或者說(shuō)要搜索內(nèi)容進(jìn)行拆分,得到更多的關(guān)鍵詞,然后去匹配。假設(shè)我們想要根據(jù) "校園愛情" 進(jìn)行查找,那么會(huì)拆分成 "校園" 和 "愛情",然后直接就能得到 1、3、4、5,再根據(jù) id 查找就可以了。
之前是逐行遍歷去確定記錄,現(xiàn)在是先根據(jù)關(guān)鍵詞來(lái)確定 id,而構(gòu)建的關(guān)鍵詞到 id 的映射便是倒排索引。所以我們也可以發(fā)現(xiàn),并不是說(shuō)使用了 ES 之后就不需要數(shù)據(jù)庫(kù)了。因?yàn)閿?shù)據(jù)庫(kù)表的字段可能非常多,我們不會(huì)對(duì)每一個(gè)字段都建立倒排索引,而是只針對(duì)那些需要通過關(guān)鍵詞匹配的字段,將該字段的每一行都拆分成一個(gè)個(gè)的關(guān)鍵詞,然后再把所有的關(guān)鍵詞組合起來(lái),建立它們到 id 之間的映射(倒排索引)。
因此在建立倒排索引后,行數(shù)反而會(huì)增多(如果大部分詞都不一樣的話),比如原來(lái)的數(shù)據(jù)有 100 萬(wàn)行,但是拆分出來(lái)的關(guān)鍵詞有 200 萬(wàn)個(gè),那么在建立倒排索引之后也會(huì)有 200 萬(wàn)行。但我們不可能真的搜索 200 萬(wàn)次,有可能我們搜索一次就找到對(duì)應(yīng)的 id 了,因?yàn)樵诘古潘饕衅ヅ涞氖顷P(guān)鍵詞。
當(dāng)然搜索一次是理想情況,也可能是十次、一百次,因此就需要設(shè)計(jì)一個(gè)好的搜索算法以及合適的數(shù)據(jù)組織結(jié)構(gòu)來(lái)使得查詢次數(shù)最小化,而算法如何設(shè)計(jì)顯然不是我們需要操心的。并且在倒排索引中進(jìn)行關(guān)鍵詞匹配也和數(shù)據(jù)庫(kù)的 like 不一樣,前者只需要匹配單詞即可,效率要比后者高很多。
以上便是全文檢索以及倒排索引,還是很好理解的。然后再來(lái)說(shuō)說(shuō) Lucene,其實(shí) Lucene 就是一個(gè) Jar 包,里面封裝了很多建立倒排索引、以及搜索相關(guān)的算法。如果你使用 Java 語(yǔ)言的話,那么只需要引入這個(gè) Jar 包,然后基于 Lucene 提供的 API 進(jìn)行開發(fā)即可。通過 Lucene 我們就可以對(duì)已有的數(shù)據(jù)建立索引,Lucene 會(huì)在本地磁盤上面組織數(shù)據(jù)的索引結(jié)構(gòu)。
什么是 ElasticSearch
了解了上面的內(nèi)容之后,再來(lái)看 ES 就簡(jiǎn)單多了。我們說(shuō) Lucene 它封裝了類似于搜索引擎的功能,但它是部署在單機(jī)上面的,如果數(shù)據(jù)量非常大、需要多機(jī)存儲(chǔ)的話該怎么辦呢。首先我們能想到的是把數(shù)據(jù)分散存儲(chǔ)在多機(jī)上,然后每臺(tái)機(jī)器各有一個(gè) Lucene。
上面的做法看似解決了數(shù)據(jù)量的問題,但其實(shí)背后還有很多缺陷,比如:
- 數(shù)據(jù)分散在多臺(tái)機(jī)器上,這些數(shù)據(jù)要怎么切分?
- 當(dāng)我們?cè)谒阉鞯臅r(shí)候,如果數(shù)據(jù)存在多臺(tái)機(jī)器上,那么是不是每臺(tái)機(jī)器都需要訪問呢?顯然這會(huì)很麻煩。
- 數(shù)據(jù)一旦分散在多臺(tái)機(jī)器上,那么如何保證建立高性能的索引?
- 數(shù)據(jù)的不丟失要如何保證,系統(tǒng)的高可用性要如何保證?
顯然上述這幾點(diǎn)都是問題,都要在考慮的范圍內(nèi)。因?yàn)槿魏慰蚣?,如果需要多機(jī)部署,那么之間就應(yīng)該具備相互通信的功能,相互協(xié)調(diào),彼此作為一個(gè)整體、像單機(jī)一樣對(duì)外提供服務(wù)。
所以 ES 就應(yīng)運(yùn)而生,它是基于 Lucene 實(shí)現(xiàn)的一個(gè)搜索引擎,同樣使用 Java 語(yǔ)言編寫。但是通過 ES 可以讓全文搜索變得更加簡(jiǎn)單,因?yàn)?Lucene 需要你有比較深的檢索相關(guān)的知識(shí),比較復(fù)雜,而 ES 將這種復(fù)雜隱藏了起來(lái),讓用戶可以通過 RESTful API 進(jìn)行查詢。
不僅如此,ES 不僅僅是為了檢索方便而封裝的 Lucene,它還解決了分布式的問題。因?yàn)?Lucene 只是一個(gè)庫(kù),如果想支持多機(jī)部署,那么你需要額外做很多的工作。而 ES 把這些全部解決了,比如:
- ES 具有分布式的文件存儲(chǔ),每個(gè)字段都可以被索引、被搜索
- 自動(dòng)維護(hù)數(shù)據(jù)在多個(gè)節(jié)點(diǎn)之間的分布,以及索引的建立、搜索請(qǐng)求的執(zhí)行
- 自動(dòng)維護(hù)數(shù)據(jù)的冗余副本,一個(gè)節(jié)點(diǎn)宕掉了,不會(huì)造成數(shù)據(jù)的丟失
- 可以輕松的擴(kuò)展到上百臺(tái)服務(wù)器,處理 PB 級(jí)結(jié)構(gòu)化或非結(jié)構(gòu)化數(shù)據(jù)
- 除了 Lucene 的檢索功能,ES 還封裝了更多的高級(jí)功能,比如聚合分析、基于地理位置的搜索等等,可以讓我們快速的開發(fā)應(yīng)用,如果要基于原生的 Lucene 實(shí)現(xiàn)是很困難的
因此什么是 ES 我們就說(shuō)完了,說(shuō)白了 ES 就是一個(gè)基于 Lucene 實(shí)現(xiàn)的搜索引擎,并且支持高可用、可伸縮、分布式。每個(gè)節(jié)點(diǎn)之上都部署一個(gè) ES,多個(gè)節(jié)點(diǎn)共同對(duì)外提供服務(wù),至于節(jié)點(diǎn)之間如何協(xié)調(diào) ES 內(nèi)部已經(jīng)幫我們做好了,無(wú)需我們關(guān)心。
此外,雖然我們一直說(shuō) ES 是一個(gè)搜索引擎,但其實(shí) ES 不僅可以用來(lái)搜索,還可以用來(lái)做數(shù)據(jù)分析。比如電商網(wǎng)站通過 ES 選取 "百褶裙" 銷量最高的十個(gè)商家,新聞網(wǎng)站通過 ES 選取訪問量最高的幾篇文章等等,顯然此時(shí)在獲取數(shù)據(jù)的同時(shí)也伴隨著數(shù)據(jù)分析。因此 ES 是一個(gè)分布式的搜索和數(shù)據(jù)分析引擎,能夠進(jìn)行全文檢索、結(jié)構(gòu)化檢索、數(shù)據(jù)分析,以及對(duì)海量數(shù)據(jù)進(jìn)行接近實(shí)時(shí)的處理。
當(dāng)然相信很多人都聽過 ELK,是用來(lái)搭建日志分析平臺(tái)的。其中 E 就是這里的 ElasticSearch,L 是 Logstash,K 是 Kibana。
- Logstash 用來(lái)做數(shù)據(jù)采集;
- ElasticSearch 負(fù)責(zé)數(shù)據(jù)分析;
- Kibana 負(fù)責(zé)數(shù)據(jù)可視化;
我們后面也會(huì)涉及到 ELK。下面總結(jié)一下 ES 的特點(diǎn):
- 可以組成大型分布式集群,處理 PB 級(jí)數(shù)據(jù),服務(wù)大公司;也可以運(yùn)行在單機(jī)或者組成小型分布式集群,服務(wù)小公司。
- ES 不是什么新技術(shù),主要是將全文檢索、數(shù)據(jù)分析以及分布式這些現(xiàn)有的技術(shù)合并在了一起,才形成了獨(dú)一無(wú)二的 ES。
- 對(duì)用戶而言是開箱即用的,非常簡(jiǎn)單,作為中小型的應(yīng)用,直接三分鐘部署一下 ES 就可以作為生產(chǎn)環(huán)境的系統(tǒng)來(lái)用了。
- 數(shù)據(jù)庫(kù)的功能面對(duì)很多領(lǐng)域是不夠用的,一些特殊的功能,像全文檢索、同義詞處理、相關(guān)度排名,復(fù)雜數(shù)據(jù)分析,海量數(shù)據(jù)的近實(shí)時(shí)處理等等,這些數(shù)據(jù)庫(kù)是不支持的,而 ES 作為一個(gè)補(bǔ)充提供了數(shù)據(jù)庫(kù)不具備的功能。
ElasticSearch 的核心概念
關(guān)于 ES,有幾個(gè)專業(yè)術(shù)語(yǔ),我們需要提前了解一下。
Cluster:集群,包含多個(gè)節(jié)點(diǎn),當(dāng)然也可以只包含一個(gè)節(jié)點(diǎn)。
Node:集群中的一個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)都有一個(gè)名稱(默認(rèn)隨機(jī)分配),節(jié)點(diǎn)的名稱還是比較重要的,尤其是在執(zhí)行運(yùn)維管理操作的時(shí)候。
Index:索引,對(duì)應(yīng) MySQL 的數(shù)據(jù)庫(kù)。
Type:類型,對(duì)應(yīng) MySQL 的表。
Document:文檔,對(duì)應(yīng) MySQL 表中的一條記錄,ES 的一個(gè) Document 就類似于一條 JSON 數(shù)據(jù)。當(dāng)然每條 JSON 數(shù)據(jù)可以有多個(gè)字段,然后字段在 ES 中被稱為 Field,對(duì)應(yīng) MySQL 中的 Column。
shard:?jiǎn)闻_(tái)機(jī)器無(wú)法存儲(chǔ)大量數(shù)據(jù),ES 可以將一個(gè)索引中的數(shù)據(jù)切分為多個(gè) shard,分布在多臺(tái)服務(wù)器上存儲(chǔ)。有了 shard 就可以橫向擴(kuò)展,存儲(chǔ)更多的數(shù)據(jù),讓搜索和操作分布到多臺(tái)服務(wù)器上去執(zhí)行,提升吞吐性能。每個(gè) shard 都是一個(gè) Lucene Index,說(shuō)白了就是 Index 的一個(gè)切片。
replica:任何一個(gè)服務(wù)器都有可能因?yàn)楣收隙礄C(jī),造成 shard 丟失,因此可以為每一個(gè) shard 創(chuàng)建多個(gè) replica 副本。replica 可以在 shard 故障時(shí)提供備用服務(wù),保證數(shù)據(jù)不丟失,此外多個(gè) replica 還可以提升搜索操作的吞吐量和性能。
默認(rèn)情況下,每個(gè) Index 會(huì)被切分成 5 個(gè) shard(建立索引時(shí)設(shè)置,設(shè)置后不能修改),被稱為 primary shard。每個(gè) primary shard 默認(rèn)會(huì)有一個(gè) replica shard(可以隨時(shí)修改)。簡(jiǎn)單說(shuō)的話,每個(gè) Index 默認(rèn)會(huì)被分成 5 個(gè) shard,每個(gè) shard 會(huì)有一個(gè) replica。
當(dāng)然啦,在 7.x 之前每個(gè) Index 默認(rèn)有 5 個(gè) shard,但從 7.x 開始每個(gè) Index 默認(rèn)只有 1 個(gè) shard。
因此在概念上,ES 和關(guān)系型數(shù)據(jù)庫(kù)還是有一些共同之處的。
圖片
需要注意的是,隨著 ES 的發(fā)展,Type 的概念逐漸在弱化,因?yàn)槿乃饕哪康氖墙㈥P(guān)鍵詞到 id 的映射,所以 Type 和全文索引的概念是沖突的。在 ES 6.x 中,已經(jīng)規(guī)定一個(gè) Index 下只能包含一個(gè) Type,而到 ES 7.x 時(shí),Type 的概念就被完全移除了。
安裝 ElasticSearch
下面來(lái)安裝 ES,這里我使用的是云服務(wù)器,操作系統(tǒng)是 CentOS 7。由于 ES 是基于 Java 語(yǔ)言編寫的,所以理論上在安裝 ES 之前要先安裝 JDK,但 ES 從 8.x 開始已經(jīng)自帶 JDK 了,因此我們就不需要再單獨(dú)安裝了。
然后去 ES 官網(wǎng)下載相應(yīng)的安裝包,這里我下載的是最新版 8.11.3,然后上傳到服務(wù)器,并解壓到 /opt 目錄中。當(dāng)然,如果你的節(jié)點(diǎn)上安裝了 Docker,那么也可以基于容器啟動(dòng)。
圖片
安裝成功,然后看一下 ES 的主目錄,是不是很熟悉呢。所有 Java 編寫的大數(shù)據(jù)組件都是類似的,每個(gè)目錄作用如下:
- bin 目錄放一些啟動(dòng)腳本、以及用于命令行操作的腳本;
- config 目錄放一些配置文件;
- lib 目錄存放程序依賴的 jar 包;
- logs 目錄負(fù)責(zé)存放日志文件;
- modules 目錄存放功能模塊;
- plugins 目錄存放一些插件。
然后里面還有一個(gè) jdk 目錄,也就是 Java 環(huán)境,所以即使當(dāng)前的系統(tǒng)沒有安裝,也是沒關(guān)系的。
下面我們啟動(dòng) ES,不過啟動(dòng)之前需要修改一下配置文件 config/elasticsearch.yml。
# ES 默認(rèn)只允許本機(jī)訪問,將其修改為 0.0.0.0
network.host: 0.0.0.0
# 端口默認(rèn)為 9200
http.port: 9200
然后再創(chuàng)建用戶,因?yàn)?ES 要求不能以 root 用戶啟動(dòng),因此我們要?jiǎng)?chuàng)建一個(gè)用戶,并賦予它相關(guān)權(quán)限。
# 創(chuàng)建一個(gè)組 es
groupadd es
# 創(chuàng)建一個(gè)用戶 es,并關(guān)聯(lián)到組 es 中
useradd es -g es
# 賦予它 ES 目錄的操作權(quán)限
chown es:es /opt/elasticsearch-8.11.3/ -R
下面切換用戶,進(jìn)入 ES 目錄中,輸入 bin/elasticsearch 啟動(dòng) ES。如果你配置了環(huán)境變量,那么直接輸入 elasticsearch 就行。
但如果你啟動(dòng)時(shí)發(fā)現(xiàn)報(bào)了下面這個(gè)錯(cuò),那么說(shuō)明空間不足。
圖片
此時(shí)應(yīng)該修改 config/jvm.options 配置文件。
# 設(shè)置 JVM 的初始內(nèi)存為 1G,此值可以與 -Xmx 相同
# 避免每次垃圾回收完成后 JVM 重新分配內(nèi)存
-Xms1g
# 設(shè)置 JVM 最大可用內(nèi)存為 1G
-Xmx1g
然后再來(lái)啟動(dòng) ES,默認(rèn)是以前臺(tái)啟動(dòng)的。但如果你發(fā)現(xiàn)輸出一堆日志信息后,進(jìn)程又退出了,并且最后輸出了 ERROR: Elasticsearch exited unexpectedly, with exit code 78。那么你需要切換回 root 用戶,然后執(zhí)行如下命令:
sysctl -w vm.max_map_count=262144
然后再打開 /etc/security/limits.conf,并在里面追加如下內(nèi)容。
es hard nofile 65536
es soft nofile 65536
這里的 es 就是剛才創(chuàng)建的用戶,如果你創(chuàng)建的用戶不叫 es,那么記得修改。
完事之后,再切換回 es 用戶,再次啟動(dòng),會(huì)發(fā)現(xiàn)啟動(dòng)成功。然后我們測(cè)試一下,瀏覽器中輸入 http://ip:9200 ,看看能否返回內(nèi)容。
然而很不幸,會(huì)發(fā)現(xiàn)無(wú)法訪問,并且 ES 會(huì)輸出如下內(nèi)容:
圖片
這是因?yàn)?ES 默認(rèn)只允許通過 HTTPS 訪問,如果想支持 HTTP,那么需要再次修改配置文件。
打開 config/elasticsearch.yml,在里面配置如下內(nèi)容:
# 是否需要用戶名密碼,這里改成 false
xpack.security.enabled: false
# 是否開啟 SSL 認(rèn)證,這里將 enabled 給改成 false
# 否則只允許 https 請(qǐng)求,而 http 請(qǐng)求會(huì)被拒絕
xpack.security.http.ssl:
enabled: true
keystore.path: certs/http.p12
然后重新啟動(dòng) ES,此時(shí)再訪問 ip:9200 就沒有問題了,會(huì)返回如下內(nèi)容。
圖片
返回了一條 JSON,我們說(shuō) ES 的 Document(文檔)就類似于一條 JSON,其字段就是 Field。然后里面的 name 字段表示節(jié)點(diǎn)名稱,cluster_name 表示集群名稱,這些都可以通過配置文件 elasticsearch.yml 進(jìn)行修改,至于其它字段就見名知意了。
到目前為止,整個(gè) ES 算是啟動(dòng)成功了,但目前是前臺(tái)啟動(dòng),我們需要改成后臺(tái)啟動(dòng)。
bin/elasticsearch -d
只需要在結(jié)尾加一個(gè) -d 即可。
小結(jié)
到目前為止,我們就介紹了什么是 ES,以及它解決了什么問題。然后了解了它的核心概念,以及安裝方式。