我是啤酒就辣條。
但行好事,莫問前程。
Elasticsearch是什么?
Elasticsearch是一個基於文檔的NoSQL數據庫,是一個分布式
、RESTful
風格的搜索和數據分析引擎,同時也是Elastic Stack
的核心,集中存儲數據。Elasticsearch、Logstash、Kibana經常被用作日志分析系統,俗稱ELK。
說白了,就是一個數據庫,搜索賊快(但是插入更新較慢,要不然其他數據庫別玩了)。速度快,還可以進行分詞,非常適合做搜索
,例如商城的商品搜索。為什么快,后面講原理的時候會說,不單單是緩存的問題,原理非常精彩。而且它是nosql的,數據格式可以隨便造。Elasticsearch還為我們提供了豐富的RESTful風格的API,寫代碼的成本極低。最后它支持分布式,高性能(搜索快),高可用(某些節點宕機可以接着用),可伸縮(可以方便的增加節點,解決物理內存上線問題),適合分布式系統開發。
Elasticsearch基本概念
為了快速了解Elasticsearch(后面可能會簡稱為ES),可以與mysql幾個概念做個對比。
Elasticsearch | Mysql |
---|---|
字段(Filed) | 屬性(列) |
文檔(Document) | 記錄(行) |
類型(Type) | 表 |
索引(Index) | 數據庫 |
是不是清楚多了?我們說Elasticsearch是基於文檔的,就是因為記錄元素(被搜索的最小單位)是文檔。例如下面就是一個文檔,
{
"email": "john@smith.com",
"first_name": "John",
"last_name": "Smith",
"info": {
"bio": "Eco-warrior and defender of the weak",
"age": 25,
"interests": [ "dolphins", "whales" ]
},
"join_date": "2014/05/01"
}
文檔格式看起來很像Json吧。email
、first_name
等等就是Filed
。由於結構是Json,所以value值就很方便放任意類型,這就是nosql的好處。
文檔(Document)
ES中的一個對象將來會和Java代碼中的一個對象對應。文檔的每一個Filed
可以是任意類型,但是一旦某索引(Index)
(我們描述的時候,略過Type
,但是Type
依然存在)中插入了一個文檔,某Filed
被第一次使用,ES就會設置好此Filed
的類型。例如你插入user的name是字符串類型,以后再插入文檔,name字段必須是字符串類型。所以,建議在插入文檔之前,先設置好每個Filed
的類型。
如果插入文檔的時候,不指定id,ES會幫助我們自動生成一個id,建議id是數字類型,這樣搜索會快速很多。商城系統中的商品id建議使用雪花算法生成,這樣既避免了自增id的安全性問題,又解決了字符串id檢索慢的問題。
類型(Type)
關於Type
,類型概念,在6.x版本中,一個索引(Index)可以擁有多個Type
。在7.x版本(目前最新版本),一個索引只能擁有一個Type
,默認的type就是_doc
,在7.x版本中,已經建議刪除了。在未來的8.x版本會徹底刪除。但是在7.x版本中,一個文檔還必須歸屬於一個類型。
索引(Index)
都說ES中的索引類似於mysql中的數據庫,我覺得未來索引有成為mysql中表概念的潛質。我們把相同特征(Filed數量和類型基本相同)的文檔放到同一個索引(index)里面。這樣方便提前通過mapping來規定各個Filed的類型。另外,索引名稱必須全部小寫,所以不建議寫成駝峰式。
節點(Node)與分片(Shard)
由於生產環境下ES基本都是集群部署的,所以一定少不了節點
的概念,一個節點就是一個ES實例,就是一個Java進程,這些Java進程部署在不同的服務器上,增加ES可用性。
ES節點根據功能可以分為三種:
- 主節點:職責是和集群操作相關的內容,如創建或刪除索引,跟蹤哪些節點是群集的一部分,並決定哪些分片分配給相關的節點。每個節點都可訪問集群的狀態,但是只有主節點可以修改集群的狀態。
- 數據節點:數據節點主要是儲存數據的節點,對文檔進行增刪改查,聚合操作等等,數據節點對cpu,內存,io要求較高,當資源不夠的時候,可以增加新的節點,很方便的進行數據拓展。
- 客戶端節點:本節點主要處理路由請求,分發索引的操作。實際上主節點和數據節點也有路由轉發的功能,但是為了提高效率,還是建議生產環境單獨創建客戶端節點。
分片類似於mysql中的分表,在一個索引拆分成幾個小索引,分布在不同的節點(不同服務器)上,每個小索引都具有完備的功能,當客戶端發來請求的時候,客戶端節點找到合適的分片上的小索引,進行數據查詢,這一過程對於用戶來說都是透明的,用戶表面上看只是在操作一個索引。利用分片,可以避免單個節點的物理限制,還可以增加吞吐量。建議最開始一個索引要用多少分片設計好,因為修改分片數量是個相當麻煩的過程。
作為分布式的數據庫,ES必須為咱們提供數據冗余功能,這就是分片副本,就是將某個分片copy一份放到其他節點上。注意,這里分片和分片副本必須在不同的節點上!分片副本也可以提高吞吐量。分片副本不同於分片,可以很方便的進行修改。
說完了所有概念,再去看本節最開始那張圖,有一個索引,分了3分片在三個節點上,並且每個分片在不同的節點上有分片副本。
Elasticsearch索引原理
看完上面的內容,你對Elasticsearch有了基本的認識,再去看基本操作(我后面要寫一篇基操博客),就可以在項目中使用Elasticsearch了。此刻你可以喘口氣,以放松的心態看后面的內容。下面我們就講講索引為什么快?
首先,我們知道mysql底層數據結構使用的是B+Tree
,這種BTree
,將搜索時間復雜度變成了logN,已經很快了,我們Elasticsearch要比它還快。Elasticsearch是怎么做的呢?首先儲存結構要優化,然后再提高下和磁盤的交互效率。
先說Elasticsearch索引結構,叫做倒排索引
,啥是倒排索引呢?它的大概邏輯如下:
為了講清楚這個概念,我們先看個例子,如下為我們user的數據:
ID | Name | Age |
---|---|---|
1 | Kate | 24 |
2 | John | 24 |
3 | Bill | 29 |
4 | Kate | 26 |
5 | Brand | 29 |
Elasticsearch會為以上數據建立兩個索引樹:
Term | Posting List |
---|---|
Kate | 1,4 |
Brand | 5 |
John | 2 |
Bill | 3 |
Term | Posting List |
---|---|
24 | 1,2 |
26 | 4 |
29 | 3,5 |
以上的索引樹就叫做倒排索引,每個Filed
字段對應着一組Term
,每個Term
后面跟着的id(還記着嗎,這個主鍵用戶不指定就會自動生成,所以一定存在)就是Posting List
,它是一組id,有了id再去磁盤中對應的文檔就so fast了。
你有沒有發現,Term
如果按序找會快點,將Term
按序排,在進行二分查找,是不是速度就跟BTree
一樣了,時間復雜度為LogN。這個有序的Term
組就是Term Dictionary
。
那么問題又來了,比如說數據庫中有name前綴為A的同學1000萬個,前綴為Z的同學有3個,我要查前綴為Z的同學,那二分查找不也很多次嗎,所以,Elasticsearch把每個開頭的地方標記一下,拿出來,再放到一顆樹里,速度不是就快了嘛。這棵樹就是Term Index
。Term Index
前綴不一定是第一個字符,比如A、Ab、Abz,這種都可以在Term Index
樹里。並且Term Dictionary
可能會太大,會被放到磁盤中,避免內存占用太多。
再看下面這張結構圖是不是清楚多了。
由於Term Index
被放到內存中,所以最好壓縮一下,減少內存使用,壓縮使用的是FST
,這個東西講起來比較復雜,反正就是能壓縮,內存變小就好了。
Term
壓縮完了,那么Posting List
是不是也可以壓縮一下,省省空間啊?既然都是id,使用過redis的同學瞬間會想到bitMap
,就是有個巨大的數組,儲存着0或1,有就是1,沒有就是0。例如上面的3、5放在BitMap中就是 1,0,1,0,0,0。雖說空間已經明顯小多了,但是如果一個Posting List
只儲存着1,10000001這兩個id,最后產生的數字是不是過大呢。於是乎,Roaring bitmaps
就出來了,進行了一次指數降級,簡單點說就是取商和余數儲存,被除數是65535。例如 1000,62101,131385,196658
, 這幾個id,首先分組,分組規則就是商一樣,例如上面id可分組為[(0,1000),(0,62101)],[],[(2,6915)],[(3,53)]。注意,沒有商為1的值,我用空數組表示。此時,將某個組中的數字放到一個bitmap中。