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