ES來源
Elasticsearch 來源于作者 Shay Banon 的第一個開源項目Compass庫,而這個庫的最初目的只是為了給Shay當(dāng)時正在學(xué)廚師的妻子做一個菜譜的搜索引擎。
馬上聯(lián)想到當(dāng)年P(guān)interest的創(chuàng)意來源也是創(chuàng)始人Ben Silbermann為了方便他的女朋友尋找訂婚戒指,可以圖釘隨手粘貼同一個頁面進(jìn)行對比。
是的!程序猿才是最會浪漫的人
ES基礎(chǔ)
數(shù)據(jù)模型
邏輯概念
ES本身是schema less的,有比較特殊的字段需要通過Mapping設(shè)置一下,每個數(shù)據(jù)點就是一行數(shù)據(jù)Document,ES數(shù)據(jù)分類通過Index這層完成的
Elassticsearch的基礎(chǔ)概念-數(shù)據(jù)模型,如上圖把ES數(shù)據(jù)模型概念和傳統(tǒng)數(shù)據(jù)庫做了對比。
- index 對應(yīng)db 庫database庫
- type 對應(yīng)db 表table表(廢棄)
- doc 對應(yīng)db 行 row
- field 對應(yīng)db 字段 column
物理存儲
首先分為兩層,一個是ES,下面是Lucene,一個ES集群有多個node組成的。一個ES實例會承載一些Shard,多個shard會落到不同機器上面,P1和P2是兩個不同的Shard,并且shard有主從的概念,對于ES每個Shard落地下來是一個Lucene Index。
節(jié)點(Node)
運行了單個實例的ES主機稱為節(jié)點,它是集群的一個成員,可以存儲數(shù)據(jù)、參與集群索引及搜索操作。類似于集群,節(jié)點靠其名稱進(jìn)行標(biāo)識,默認(rèn)為啟動時自動生成的隨機Marvel字符名稱。用戶可以按需要自定義任何希望使用的名稱,但出于管理的目的,此名稱應(yīng)該盡可能有較好的識別性。節(jié)點通過為其配置的ES集群名稱確定其所要加入的集群。
分片(Shard)和副本(Replica)
ES的“分片(shard)”機制可將一個索引內(nèi)部的數(shù)據(jù)分布地存儲于多個節(jié)點,它通過將一個索引切分為多個底層物理的Lucene索引完成索引數(shù)據(jù)的分割存儲功能,這每一個物理的Lucene索引稱為一個分片(shard)。每個分片其內(nèi)部都是一個全功能且獨立的索引,因此可由集群中的任何主機存儲。創(chuàng)建索引時,用戶可指定其分片的數(shù)量,默認(rèn)數(shù)量為5個。
Shard有兩種類型:primary和replica,即主shard及副本shard。Primary shard用于文檔存儲,每個新的索引會自動創(chuàng)建5個Primary(最新版改成1個了) shard,當(dāng)然此數(shù)量可在索引創(chuàng)建之前通過配置自行定義,不過,一旦創(chuàng)建完成,其Primary shard的數(shù)量將不可更改。Replica shard是Primary Shard的副本,用于冗余數(shù)據(jù)及提高搜索性能。每個Primary shard默認(rèn)配置了一個Replica shard,但也可以配置多個,且其數(shù)量可動態(tài)更改。ES會根據(jù)需要自動增加或減少這些Replica shard的數(shù)量。
ES集群可由多個節(jié)點組成,各Shard分布式地存儲于這些節(jié)點上。
ES可自動在節(jié)點間按需要移動shard,例如增加節(jié)點或節(jié)點故障時。簡而言之,分片實現(xiàn)了集群的分布式存儲,而副本實現(xiàn)了其分布式處理及冗余功能。
數(shù)據(jù)持久化
Lucence索引原理
新接到的數(shù)據(jù)寫入新的索引文件里
動態(tài)更新索引時候,不修改已經(jīng)生成的倒排索引,而是新生成一個段(segment)
每個段都是一個倒排索引,然后另外使用一個commit文件記錄索引內(nèi)所有的segment,而生成segment的數(shù)據(jù)來源則放在內(nèi)存的buffer中
持久化主要有四個步驟,write->refresh->flush->merge
- 寫入in-memory buffer和事務(wù)日志translog
- 定期refresh到段文件segment中 可以被檢索到
- 定期flush segement落盤 清除translog
- 定期合并segment 優(yōu)化流程
write
es每新增一條數(shù)據(jù)記錄時,都會把數(shù)據(jù)雙寫到translog和in-memory buffer內(nèi)存緩沖區(qū)中
這時候還不能會被檢索到,而數(shù)據(jù)必須被refresh到segment后才能被檢索到
refresh
默認(rèn)情況下es每隔1s執(zhí)行一次refresh,太耗性能,可以通過index.refresh_interval來修改這個刷新時間間隔。
整個refresh具體做了如下事情
- 所有在內(nèi)存緩沖區(qū)的文檔被寫入到一個新的segment中,但是沒有調(diào)用fsync,因此數(shù)據(jù)有可能丟失,此時segment首先被寫到內(nèi)核的文件系統(tǒng)中緩存
- segment被打開是的里面的文檔能夠被見檢索到
-
清空內(nèi)存緩沖區(qū)in-memory buffer,清空后如下圖
refresh操作.png
flush
隨著translog文件越來越大時要考慮把內(nèi)存中的數(shù)據(jù)刷新到磁盤中,這個過程叫flush
- 把所有在內(nèi)存緩沖區(qū)中的文檔寫入到一個新的segment中
- 清空內(nèi)存緩沖區(qū)
- 往磁盤里寫入commit point信息
- 文件系統(tǒng)的page cache(segments) fsync到磁盤
-
刪除舊的translog文件,因此此時內(nèi)存中的segments已經(jīng)寫入到磁盤中,就不需要translog來保障數(shù)據(jù)安全了,flush后效果如下
flush操作.png
flush和fsync的區(qū)別
flush是把內(nèi)存中的數(shù)據(jù)(包括translog和segments)都刷到磁盤,而fsync只是把translog刷新的磁盤(確保數(shù)據(jù)不丟失)。
segment合并
通過每隔一秒的自動刷新機制會創(chuàng)建一個新的segment,用不了多久就會有很多的segment。segment會消耗系統(tǒng)的文件句柄,內(nèi)存,CPU時鐘。最重要的是,每一次請求都會依次檢查所有的segment。segment越多,檢索就會越慢。
ES通過在后臺merge這些segment的方式解決這個問題。小的segment merge到大的
這個過程也是那些被”刪除”的文檔真正被清除出文件系統(tǒng)的過程,因為被標(biāo)記為刪除的文檔不會被拷貝到大的segment中。
- 當(dāng)在建立索引過程中,refresh進(jìn)程會創(chuàng)建新的segments然后打開他們以供索引。
-
merge進(jìn)程會選擇一些小的segments然后merge到一個大的segment中。這個過程不會打斷檢索和創(chuàng)建索引。一旦merge完成,舊的segments將被刪除。
段合并后.png
新的segment被flush到磁盤
一個新的提交點被寫入,包括新的segment,排除舊的小的segments
新的segment打開以供索引
舊的segments被刪除
下面補充介紹下translog
Translog
Lucence基于節(jié)點宕機的考慮,每次寫入都會落盤Translog,類似db binlog,不同的是db binlog 通過expire_logs_days=7 設(shè)定過期時間以天為單位 默認(rèn)7天
而translog 是每次flush后會清除 可以通過幾個維度的設(shè)定清除策略
- index.translog.flush_threshold_ops,執(zhí)行多少次操作后執(zhí)行一次flush,默認(rèn)無限制
- index.translog.flush_threshold_size,translog的大小超過這個參數(shù)后flush,默認(rèn)512mb
- index.translog.flush_threshold_period,多長時間強制flush一次,默認(rèn)30m
- index.translog.interval,es多久去檢測一次translog是否滿足flush條件
translog日志提供了一個所有還未被flush到磁盤的操作的持久化記錄。當(dāng)ES啟動的時候,它會使用最新的commit point從磁盤恢復(fù)所有已有的segments,然后將重現(xiàn)所有在translog里面的操作來添加更新,這些更新發(fā)生在最新的一次commit的記錄之后還未被fsync。
translog日志也可以用來提供實時的CRUD。當(dāng)你試圖通過文檔ID來讀取、更新、刪除一個文檔時,它會首先檢查translog日志看看有沒有最新的更新,然后再從響應(yīng)的segment中獲得文檔。這意味著它每次都會對最新版本的文檔做操作,并且是實時的。
理論上設(shè)定好這個過期策略,在flush之前把translog拿到后做雙機房同步或者進(jìn)一步的消息通知處理,還可以有很大作為,可行性還有待嘗試。
寫操作
主分片+至少一個副分片全部寫成功后才返回成功
具體流程:
- 客戶端向node1發(fā)起寫入文檔請求
- Node1根據(jù)文檔ID(_id字段)計算出該文檔屬于分片shard0,然后將請求路由到Node3 的主分片P0上
路由公式 shard = hash(routing) % number_of_primary_shards
- Node3在p0上執(zhí)行了寫入請求后,如果成功則將請求并行的路由至Node1 Node2它的副本分片R0上,且都成功后Node1再報告至client
wait_for_active_shards 來配置副本分配同步策略
- 設(shè)置為1 表示僅寫完主分片就返回
- 設(shè)置為all 表示等所有副本分片都寫完才能返回
- 設(shè)置為1-number_of_replicas+1之間的數(shù)值,比如2個副本分片,有1個寫成功即可返回
timeout 控制集群異常副本同步分片不可用時候的等待時間
讀操作
一個文檔可以在任意主副分片上讀取
讀取流程:
- 客戶端發(fā)起讀請求到Node1
- Node1根據(jù)文檔ID(_id字段)計算出該文檔屬于分片shard0,在所有節(jié)點上都有,這次它根據(jù)負(fù)載均衡將請求路由至Node2
- Node2將文檔返回給Node1,Node1將文檔返回給client
更新操作
更新操作其實就是先讀然后寫
更新流程:
- 客戶端將更新請求發(fā)給Node1
- Node1根據(jù)文檔ID(_id字段)計算出該文檔屬于分片shard0,而其主分片在Node上,于是將請求路由到Node3
- Node3從p0讀取文檔,改變source字段的json內(nèi)容,然后將修改后的數(shù)據(jù)在P0重新做索引。如果此時該文檔被其他進(jìn)程修改,那么將重新執(zhí)行3步驟,這個過程如果超過retryon_confilct設(shè)置的重試次數(shù),就放棄。
- 如果Node3成功更新了文檔,它將并行的將新版本的文檔同步到Node1 Node2的副本分片上重新建立索引,一旦所有的副本報告成功,Node3向被請求的Node1節(jié)點返回成功,然后Node1向client返回成功
更新和刪除
由于segments是不變的,所以文檔不能從舊的segments中刪除,也不能在舊的segments中更新來映射一個新的文檔版本。取之的是,每一個提交點都會包含一個.del文件,列舉了哪一個segment的哪一個文檔已經(jīng)被刪除了。 當(dāng)一個文檔被”刪除”了,它僅僅是在.del文件里被標(biāo)記了一下。被”刪除”的文檔依舊可以被索引到,但是它將會在最終結(jié)果返回時被移除掉。
文檔的更新同理:當(dāng)文檔更新時,舊版本的文檔將會被標(biāo)記為刪除,新版本的文檔在新的segment中建立索引。也許新舊版本的文檔都會本檢索到,但是舊版本的文檔會在最終結(jié)果返回時被移除。
每個程序員都是手指會跳舞的藝術(shù)家~
看完了是不是覺得蠻浪漫,未完待續(xù)~