我是啤酒就辣條。 但行好事,莫問前程。
Elasticsearch是一個基于文檔的NoSQL資料庫,是一個<code>分布式</code>、<code>RESTful</code>風格的搜尋和資料分析引擎,同時也是<code>Elastic Stack</code>的核心,集中存儲資料。Elasticsearch、Logstash、Kibana經常被用作日志分析系統,俗稱ELK。
說白了,就是一個資料庫,搜尋賊快(但是插入更新較慢,要不然其他資料庫别玩了)。速度快,還可以進行分詞,非常适合做<code>搜尋</code>,例如商城的商品搜尋。為什麼快,後面講原理的時候會說,不單單是緩存的問題,原理非常精彩。而且它是nosql的,資料格式可以随便造。Elasticsearch還為我們提供了豐富的RESTful風格的API,寫代碼的成本極低。最後它支援分布式,高性能(搜尋快),高可用(某些節點當機可以接着用),可伸縮(可以友善的增加節點,解決實體記憶體上線問題),适合分布式系統開發。
為了快速了解Elasticsearch(後面可能會簡稱為ES),可以與mysql幾個概念做個對比。
Elasticsearch
Mysql
字段(Filed)
屬性(列)
文檔(Document)
記錄(行)
類型(Type)
表
索引(Index)
資料庫
是不是清楚多了?我們說Elasticsearch是基于文檔的,就是因為記錄元素(被搜尋的最小機關)是文檔。例如下面就是一個文檔,
文檔格式看起來很像Json吧。<code>email</code>、<code>first_name</code>等等就是<code>Filed</code>。由于結構是Json,是以value值就很友善放任意類型,這就是nosql的好處。
ES中的一個對象将來會和Java代碼中的一個對象對應。文檔的每一個<code>Filed</code>可以是任意類型,但是一旦某<code>索引(Index)</code>(我們描述的時候,略過<code>Type</code>,但是<code>Type</code>依然存在)中插入了一個文檔,某<code>Filed</code>被第一次使用,ES就會設定好此<code>Filed</code>的類型。例如你插入user的name是字元串類型,以後再插入文檔,name字段必須是字元串類型。是以,建議在插入文檔之前,先設定好每個<code>Filed</code>的類型。
如果插入文檔的時候,不指定id,ES會幫助我們自動生成一個id,建議id是數字類型,這樣搜尋會快速很多。商城系統中的商品id建議使用雪花算法生成,這樣既避免了自增id的安全性問題,又解決了字元串id檢索慢的問題。
關于<code>Type</code>,類型概念,在6.x版本中,一個索引(Index)可以擁有多個<code>Type</code>。在7.x版本(目前最新版本),一個索引隻能擁有一個<code>Type</code>,預設的type就是<code>_doc</code>,在7.x版本中,已經建議删除了。在未來的8.x版本會徹底删除。但是在7.x版本中,一個文檔還必須歸屬于一個類型。
都說ES中的索引類似于mysql中的資料庫,我覺得未來索引有成為mysql中表概念的潛質。我們把相同特征(Filed數量和類型基本相同)的文檔放到同一個索引(index)裡面。這樣友善提前通過mapping來規定各個Filed的類型。另外,索引名稱必須全部小寫,是以不建議寫成駝峰式。

由于生産環境下ES基本都是叢集部署的,是以一定少不了<code>節點</code>的概念,一個節點就是一個ES執行個體,就是一個Java程序,這些Java程序部署在不同的伺服器上,增加ES可用性。
ES節點根據功能可以分為三種:
主節點:職責是和叢集操作相關的内容,如建立或删除索引,跟蹤哪些節點是群集的一部分,并決定哪些分片配置設定給相關的節點。每個節點都可通路叢集的狀态,但是隻有主節點可以修改叢集的狀态。
資料節點:資料節點主要是儲存資料的節點,對文檔進行增删改查,聚合操作等等,資料節點對cpu,記憶體,io要求較高,當資源不夠的時候,可以增加新的節點,很友善的進行資料拓展。
用戶端節點:本節點主要處理路由請求,分發索引的操作。實際上主節點和資料節點也有路由轉發的功能,但是為了提高效率,還是建議生産環境單獨建立用戶端節點。
分片類似于mysql中的分表,在一個索引拆分成幾個小索引,分布在不同的節點(不同伺服器)上,每個小索引都具有完備的功能,當用戶端發來請求的時候,用戶端節點找到合适的分片上的小索引,進行資料查詢,這一過程對于使用者來說都是透明的,使用者表面上看隻是在操作一個索引。利用分片,可以避免單個節點的實體限制,還可以增加吞吐量。建議最開始一個索引要用多少分片設計好,因為修改分片數量是個相當麻煩的過程。
作為分布式的資料庫,ES必須為咱們提供資料備援功能,這就是分片副本,就是将某個分片copy一份放到其他節點上。注意,這裡分片和分片副本**必須在不同的節點上!**分片副本也可以提高吞吐量。分片副本不同于分片,可以很友善的進行修改。
說完了所有概念,再去看本節最開始那張圖,有一個索引,分了3分片在三個節點上,并且每個分片在不同的節點上有分片副本。
看完上面的内容,你對Elasticsearch有了基本的認識,再去看基本操作(我後面要寫一篇基操部落格),就可以在項目中使用Elasticsearch了。此刻你可以喘口氣,以放松的心态看後面的内容。下面我們就講講索引為什麼快?
首先,我們知道mysql底層資料結構使用的是<code>B+Tree</code>,這種<code>BTree</code>,将搜尋時間複雜度變成了logN,已經很快了,我們Elasticsearch要比它還快。Elasticsearch是怎麼做的呢?首先儲存結構要優化,然後再提高下和磁盤的互動效率。
先說Elasticsearch索引結構,叫做<code>反向索引</code>,啥是反向索引呢?它的大概邏輯如下:
為了講清楚這個概念,我們先看個例子,如下為我們user的資料:
ID
Name
Age
1
Kate
24
2
John
3
Bill
29
4
26
5
Brand
Elasticsearch會為以上資料建立兩個索引樹:
Term
Posting List
1,4
1,2
3,5
以上的索引樹就叫做反向索引,每個<code>Filed</code>字段對應着一組<code>Term</code>,每個<code>Term</code>後面跟着的id(還記着嗎,這個主鍵使用者不指定就會自動生成,是以一定存在)就是<code>Posting List</code>,它是一組id,有了id再去磁盤中對應的文檔就so fast了。
你有沒有發現,<code>Term</code>如果按序找會快點,将<code>Term</code>按序排,在進行二分查找,是不是速度就跟<code>BTree</code>一樣了,時間複雜度為LogN。這個有序的<code>Term</code>組就是<code>Term Dictionary</code>。
那麼問題又來了,比如說資料庫中有name字首為A的同學1000萬個,字首為Z的同學有3個,我要查字首為Z的同學,那二分查找不也很多次嗎,是以,Elasticsearch把每個開頭的地方标記一下,拿出來,再放到一顆樹裡,速度不是就快了嘛。這棵樹就是<code>Term Index</code>。<code>Term Index</code>字首不一定是第一個字元,比如A、Ab、Abz,這種都可以在<code>Term Index</code>樹裡。并且<code>Term Dictionary</code>可能會太大,會被放到磁盤中,避免記憶體占用太多。
再看下面這張結構圖是不是清楚多了。
由于<code>Term Index</code>被放到記憶體中,是以最好壓縮一下,減少記憶體使用,壓縮使用的是<code>FST</code>,這個東西講起來比較複雜,反正就是能壓縮,記憶體變小就好了。
<code>Term</code>壓縮完了,那麼<code>Posting List</code>是不是也可以壓縮一下,省省空間啊?既然都是id,使用過redis的同學瞬間會想到<code>bitMap</code>,就是有個巨大的數組,儲存着0或1,有就是1,沒有就是0。例如上面的3、5放在BitMap中就是 1,0,1,0,0,0。雖說空間已經明顯小多了,但是如果一個<code>Posting List</code>隻儲存着1,10000001這兩個id,最後産生的數字是不是過大呢。于是乎,<code>Roaring bitmaps</code>就出來了,進行了一次指數降級,簡單點說就是取商和餘數儲存,被除數是65535。例如 <code>1000,62101,131385,196658</code>, 這幾個id,首先分組,分組規則就是商一樣,例如上面id可分組為[(0,1000),(0,62101)],[],[(2,6915)],[(3,53)]。注意,沒有商為1的值,我用空數組表示。此時,将某個組中的數字放到一個bitmap中。