萬字長文,理解Elasticsearch和麪試總結

集羣部署

ES 部署狀況:html

5 節點(配置:8 核 64 G 1T),總計 320 G,5 T。前端

約 10+ 索引,5 分片,每日新增數據量約爲 2G,4000w 條。記錄保存 30 天。java

性能優化

# filesystem cache

你往 es 裏寫的數據,實際上都寫到磁盤文件裏去了,查詢的時候,操做系統會將磁盤文件裏的數據自動緩存到 filesystem cache 裏面去。node

es 的搜索引擎嚴重依賴於底層的 filesystem cache ,你若是給 filesystem cache 更多的內存,儘可能讓內存能夠容納全部的 idx segment file索引數據文件,那麼你搜索的時候就基本都是走內存的,性能會很是高。
mysql

性能差距究竟能夠有多大?咱們以前不少的測試和壓測,若是走磁盤通常確定上秒,搜索性能絕對是秒級別的,1 秒、5 秒、10 秒。但若是是走 filesystem cache ,是走純內存的,那麼通常來講性能比走磁盤要高一個數量級,基本上就是毫秒級的,從幾毫秒到幾百毫秒不等。git

這裏有個真實的案例。某個公司 es 節點有 3 臺機器,每臺機器看起來內存不少,64G,總內存就是 64 * 3 = 192G 。每臺機器給 es jvm heap 是 32G ,那麼剩下來留給 filesystem cache 的就是每臺機器才 32G ,總共集羣裏給 filesystem cache 的就是 32 * 3 = 96G 內存。而此時,整個磁盤上索引數據文件,在 3 臺機器上一共佔用了 1T 的磁盤容量,es 數據量是 1T ,那麼每臺機器的數據量是 300G 。這樣性能好嗎? filesystem cache 的內存才 100G,十分之一的數據能夠放內存,其餘的都在磁盤,而後你執行搜索操做,大部分操做都是走磁盤,性能確定差。程序員

歸根結底,你要讓 es 性能要好,最佳的狀況下,就是你的機器的內存,至少能夠容納你的總數據量的一半。github

根據咱們本身的生產環境實踐經驗,最佳的狀況下,是僅僅在 es 中就存少許的數據,就是你要用來搜索的那些索引,若是內存留給 filesystem cache 的是 100G,那麼你就將索引數據控制在 100G 之內,這樣的話,你的數據幾乎所有走內存來搜索,性能很是之高,通常能夠在 1 秒之內。面試

好比說你如今有一行數據。 id,name,age .... 30 個字段。可是你如今搜索,只須要根據 id,name,age 三個字段來搜索。若是你傻乎乎往 es 裏寫入一行數據全部的字段,就會致使說 90% 的數據是不用來搜索的,結果硬是佔據了 es 機器上的 filesystem cache 的空間,單條數據的數據量越大,就會致使 filesystem cahce 能緩存的數據就越少。其實,僅僅寫入 es 中要用來檢索的少數幾個字段就能夠了,好比說就寫入 es id,name,age 三個字段,而後你能夠把其餘的字段數據存在 mysql/hbase 裏,咱們通常是建議用 es + hbase 這麼一個架構。算法

hbase 的特色是適用於海量數據的在線存儲,就是對 hbase 能夠寫入海量數據,可是不要作複雜的搜索,作很簡單的一些根據 id 或者範圍進行查詢的這麼一個操做就能夠了。從 es 中根據 name 和 age 去搜索,拿到的結果可能就 20 個 doc id ,而後根據 doc id 到 hbase 裏去查詢每一個 doc id 對應的完整的數據,給查出來,再返回給前端。

寫入 es 的數據最好小於等於,或者是略微大於 es 的 filesystem cache 的內存容量。而後你從 es 檢索可能就花費 20ms,而後再根據 es 返回的 id 去 hbase 裏查詢,查 20 條數據,可能也就耗費個 30ms,可能你原來那麼玩兒,1T 數據都放 es,會每次查詢都是 5~10s,如今可能性能就會很高,每次查詢就是 50ms。

# 數據預熱

假如說,哪怕是你就按照上述的方案去作了,es 集羣中每一個機器寫入的數據量仍是超過了 filesystem cache 一倍,好比說你寫入一臺機器 60G 數據,結果 filesystem cache 就 30G,仍是有 30G 數據留在了磁盤上。

其實能夠作數據預熱

舉個例子,拿微博來講,你能夠把一些大 V,平時看的人不少的數據,你本身提早後臺搞個系統,每隔一下子,本身的後臺系統去搜索一下熱數據,刷到 filesystem cache 裏去,後面用戶實際上來看這個熱數據的時候,他們就是直接從內存裏搜索了,很快。

或者是電商,你能夠將平時查看最多的一些商品,好比說 iphone 8,熱數據提早後臺搞個程序,每隔 1 分鐘本身主動訪問一次,刷到 filesystem cache 裏去。

對於那些你以爲比較熱的、常常會有人訪問的數據,最好作一個專門的緩存預熱子系統,就是對熱數據每隔一段時間,就提早訪問一下,讓數據進入 filesystem cache 裏面去。這樣下次別人訪問的時候,性能必定會好不少。

# 冷熱分離

es 能夠作相似於 mysql 的水平拆分,就是說將大量的訪問不多、頻率很低的數據,單獨寫一個索引,而後將訪問很頻繁的熱數據單獨寫一個索引。最好是將冷數據寫入一個索引中,而後熱數據寫入另一個索引中,這樣能夠確保熱數據在被預熱以後,儘可能都讓他們留在 filesystem os cache 裏,別讓冷數據給沖刷掉

你看,假設你有 6 臺機器,2 個索引,一個放冷數據,一個放熱數據,每一個索引 3 個 shard。3 臺機器放熱數據 index,另外 3 臺機器放冷數據 index。而後這樣的話,你大量的時間是在訪問熱數據 index,熱數據可能就佔總數據量的 10%,此時數據量不多,幾乎全都保留在 filesystem cache 裏面了,就能夠確保熱數據的訪問性能是很高的。可是對於冷數據而言,是在別的 index 裏的,跟熱數據 index 不在相同的機器上,你們互相之間都沒什麼聯繫了。若是有人訪問冷數據,可能大量數據是在磁盤上的,此時性能差點,就 10% 的人去訪問冷數據,90% 的人在訪問熱數據,也無所謂了。

# document 模型設計

對於 MySQL,咱們常常有一些複雜的關聯查詢。在 es 裏該怎麼玩兒,es 裏面的複雜的關聯查詢儘可能別用,一旦用了性能通常都不太好。

最好是先在 Java 系統裏就完成關聯,將關聯好的數據直接寫入 es 中。搜索的時候,就不須要利用 es 的搜索語法來完成 join 之類的關聯搜索了。

document 模型設計是很是重要的,不少操做,不要在搜索的時候纔想去執行各類複雜的亂七八糟的操做。es 能支持的操做就那麼多,不要考慮用 es 作一些它很差操做的事情。若是真的有那種操做,儘可能在 document 模型設計的時候,寫入的時候就完成。另外對於一些太複雜的操做,好比 join/nested/parent-child 搜索都要儘可能避免,性能都不好的。

# 分頁性能優化

es 的分頁是較坑的,爲啥呢?舉個例子吧,假如你每頁是 10 條數據,你如今要查詢第 100 頁,其實是會把每一個 shard 上存儲的前 1000 條數據都查到一個協調節點上,若是你有個 5 個 shard,那麼就有 5000 條數據,接着協調節點對這 5000 條數據進行一些合併、處理,再獲取到最終第 100 頁的 10 條數據。

分佈式的,你要查第 100 頁的 10 條數據,不可能說從 5 個 shard,每一個 shard 就查 2 條數據,最後到協調節點合併成 10 條數據吧?你必須得從每一個 shard 都查 1000 條數據過來,而後根據你的需求進行排序、篩選等等操做,最後再次分頁,拿到裏面第 100 頁的數據。你翻頁的時候,翻的越深,每一個 shard 返回的數據就越多,並且協調節點處理的時間越長,很是坑爹。因此用 es 作分頁的時候,你會發現越翻到後面,就越是慢。

咱們以前也是遇到過這個問題,用 es 做分頁,前幾頁就幾十毫秒,翻到 10 頁或者幾十頁的時候,基本上就要 5~10 秒才能查出來一頁數據了。

有什麼解決方案嗎?

# 不容許深度分頁(默認深度分頁性能不好)

跟產品經理說,你係統不容許翻那麼深的頁,默認翻的越深,性能就越差。

# 相似於 app 裏的推薦商品不斷下拉出來一頁一頁的

相似於微博中,下拉刷微博,刷出來一頁一頁的,你能夠用 scroll api ,關於如何使用,自行上網搜索。

scroll 會一次性給你生成全部數據的一個快照,而後每次滑動向後翻頁就是經過遊標 scroll_id 移動,獲取下一頁下一頁這樣子,性能會比上面說的那種分頁性能要高不少不少,基本上都是毫秒級的。

可是,惟一的一點就是,這個適合於那種相似微博下拉翻頁的,不能隨意跳到任何一頁的場景。也就是說,你不能先進入第 10 頁,而後去第 120 頁,而後又回到第 58 頁,不能隨意亂跳頁。因此如今不少產品,都是不容許你隨意翻頁的,app,也有一些網站,作的就是你只能往下拉,一頁一頁的翻。

初始化時必須指定 scroll 參數,告訴 es 要保存這次搜索的上下文多長時間。你須要確保用戶不會持續不斷翻頁翻幾個小時,不然可能由於超時而失敗。

除了用 scroll api ,你也能夠用 search_after 來作, search_after 的思想是使用前一頁的結果來幫助檢索下一頁的數據,顯然,這種方式也不容許你隨意翻頁,你只能一頁頁日後翻。初始化時,須要使用一個惟一值的字段做爲 sort 字段。

1.一、設計階段調優

(1)根據業務增量需求,採起基於日期模板建立索引,經過 roll over API 滾動索引;

(2)使用別名進行索引管理;

(3)天天凌晨定時對索引作 force_merge 操做,以釋放空間;

(4)採起冷熱分離機制,熱數據存儲到 SSD,提升檢索效率;冷數據按期進行 shrink 操做,以縮減存儲;

(5)採起 curator 進行索引的生命週期管理;

(6)僅針對須要分詞的字段,合理的設置分詞器;

(7)Mapping 階段充分結合各個字段的屬性,是否須要檢索、是否須要存儲等。……..

1.二、寫入調優

(1)寫入前副本數設置爲 0;

(2)寫入前關閉 refresh_interval 設置爲-1,禁用刷新機制;

(3)寫入過程當中:採起 bulk 批量寫入;

(4)寫入後恢復副本數和刷新間隔;

(5)儘可能使用自動生成的 id。

1.三、查詢調優

(1)禁用 wildcard;

(2)禁用批量 terms(成百上千的場景);

(3)充分利用倒排索引機制,能 keyword 類型儘可能 keyword;

(4)數據量大時候,能夠先基於時間敲定索引再檢索;

(5)設置合理的路由機制。

1.四、其餘調優

部署調優,業務調優等。

上面的說起一部分,面試者就基本對你以前的實踐或者運維經驗有所評估了。

# 工做原理

# es 寫數據過程

  • 客戶端選擇一個 node 發送請求過去,這個 node 就是 coordinating node (協調節點)。

  • coordinating node 對 document 進行路由,將請求轉發給對應的 node(有 primary shard)。

  • 實際的 node 上的 primary shard 處理請求,而後將數據同步到 replica node 。

  • coordinating node 若是發現 primary node 和全部 replica node 都搞定以後,就返回響應結果給客戶端。

# es 讀數據過程

能夠經過 doc id 來查詢,會根據 doc id 進行 hash,判斷出來當時把 doc id 分配到了哪一個 shard 上面去,從那個 shard 去查詢。

  • 客戶端發送請求到任意一個 node,成爲 coordinate node 。

  • coordinate node 對 doc id 進行哈希路由,將請求轉發到對應的 node,此時會使用 round-robin 隨機輪詢算法,在 primary shard 以及其全部 replica 中隨機選擇一個,讓讀請求負載均衡。

  • 接收請求的 node 返回 document 給 coordinate node 。

  • coordinate node 返回 document 給客戶端。

# es 搜索數據過程

es 最強大的是作全文檢索,就是好比你有三條數據:

你根據 java 關鍵詞來搜索,將包含 java 的 document 給搜索出來。es 就會給你返回:java 真好玩兒啊,java 好難學啊。

  • 客戶端發送請求到一個 coordinate node 。

  • 協調節點將搜索請求轉發到全部的 shard 對應的 primary shard 或 replica shard ,均可以。

  • query phase:每一個 shard 將本身的搜索結果(其實就是一些 doc id )返回給協調節點,由協調節點進行數據的合併、排序、分頁等操做,產出最終結果。

  • fetch phase:接着由協調節點根據 doc id 去各個節點上拉取實際的 document 數據,最終返回給客戶端。

寫請求是寫入 primary shard,而後同步給全部的 replica shard;讀請求能夠從 primary shard 或 replica shard 讀取,採用的是隨機輪詢算法。

# 寫數據底層原理

先寫入內存 buffer,在 buffer 裏的時候數據是搜索不到的;同時將數據寫入 translog 日誌文件。

若是 buffer 快滿了,或者到必定時間,就會將內存 buffer 數據 refresh 到一個新的 segment file 中,可是此時數據不是直接進入 segment file 磁盤文件,而是先進入 os cache 。這個過程就是 refresh 。

每隔 1 秒鐘,es 將 buffer 中的數據寫入一個新的 segment file ,每秒鐘會產生一個新的磁盤文件 segment file ,這個 segment file 中就存儲最近 1 秒內 buffer 中寫入的數據。

可是若是 buffer 裏面此時沒有數據,那固然不會執行 refresh 操做,若是 buffer 裏面有數據,默認 1 秒鐘執行一次 refresh 操做,刷入一個新的 segment file 中。

操做系統裏面,磁盤文件其實都有一個東西,叫作 os cache ,即操做系統緩存,就是說數據寫入磁盤文件以前,會先進入 os cache ,先進入操做系統級別的一個內存緩存中去。只要 buffer 中的數據被 refresh 操做刷入 os cache 中,這個數據就能夠被搜索到了。

爲何叫 es 是準實時的? NRT ,全稱 near real-time 。默認是每隔 1 秒 refresh 一次的,因此 es 是準實時的,由於寫入的數據 1 秒以後才能被看到。能夠經過 es 的 restful api 或者 java api ,手動執行一次 refresh 操做,就是手動將 buffer 中的數據刷入 os cache 中,讓數據立馬就能夠被搜索到。只要數據被輸入 os cache 中,buffer 就會被清空了,由於不須要保留 buffer 了,數據在 translog 裏面已經持久化到磁盤去一份了。

重複上面的步驟,新的數據不斷進入 buffer 和 translog,不斷將 buffer 數據寫入一個又一個新的 segment file 中去,每次 refresh 完 buffer 清空,translog 保留。隨着這個過程推動,translog 會變得愈來愈大。當 translog 達到必定長度的時候,就會觸發 commit 操做。

commit 操做發生第一步,就是將 buffer 中現有數據 refresh 到 os cache 中去,清空 buffer。而後,將一個 commit point 寫入磁盤文件,裏面標識着這個 commit point 對應的全部 segment file ,同時強行將 os cache 中目前全部的數據都 fsync 到磁盤文件中去。最後清空 現有 translog 日誌文件,重啓一個 translog,此時 commit 操做完成。

這個 commit 操做叫作 flush 。默認 30 分鐘自動執行一次 flush ,但若是 translog 過大,也會觸發 flush 。flush 操做就對應着 commit 的全過程,咱們能夠經過 es api,手動執行 flush 操做,手動將 os cache 中的數據 fsync 強刷到磁盤上去。

translog 日誌文件的做用是什麼?你執行 commit 操做以前,數據要麼是停留在 buffer 中,要麼是停留在 os cache 中,不管是 buffer 仍是 os cache 都是內存,一旦這臺機器死了,內存中的數據就全丟了。因此須要將數據對應的操做寫入一個專門的日誌文件 translog 中,一旦此時機器宕機,再次重啓的時候,es 會自動讀取 translog 日誌文件中的數據,恢復到內存 buffer 和 os cache 中去。

translog 其實也是先寫入 os cache 的,默認每隔 5 秒刷一次到磁盤中去,因此默認狀況下,可能有 5 秒的數據會僅僅停留在 buffer 或者 translog 文件的 os cache 中,若是此時機器掛了,會丟失 5 秒鐘的數據。可是這樣性能比較好,最多丟 5 秒的數據。也能夠將 translog 設置成每次寫操做必須是直接 fsync 到磁盤,可是性能會差不少。

實際上你在這裏,若是面試官沒有問你 es 丟數據的問題,你能夠在這裏給面試官炫一把,你說,其實 es 第一是準實時的,數據寫入 1 秒後能夠搜索到;可能會丟失數據的。有 5 秒的數據,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盤上,此時若是宕機,會致使 5 秒的數據丟失

總結一下,數據先寫入內存 buffer,而後每隔 1s,將數據 refresh 到 os cache,到了 os cache 數據就能被搜索到(因此咱們才說 es 從寫入到能被搜索到,中間有 1s 的延遲)。每隔 5s,將數據寫入 translog 文件(這樣若是機器宕機,內存數據全沒,最多會有 5s 的數據丟失),translog 大到必定程度,或者默認每隔 30mins,會觸發 commit 操做,將緩衝區的數據都 flush 到 segment file 磁盤文件中。

數據寫入 segment file 以後,同時就創建好了倒排索引。

# 刪除/更新數據底層原理

若是是刪除操做,commit 的時候會生成一個 .del 文件,裏面將某個 doc 標識爲 deleted 狀態,那麼搜索的時候根據 .del 文件就知道這個 doc 是否被刪除了。

若是是更新操做,就是將原來的 doc 標識爲 deleted 狀態,而後新寫入一條數據。

buffer 每 refresh 一次,就會產生一個 segment file ,因此默認狀況下是 1 秒鐘一個 segment file ,這樣下來 segment file 會愈來愈多,此時會按期執行 merge。每次 merge 的時候,會將多個 segment file 合併成一個,同時這裏會將標識爲 deleted 的 doc 給物理刪除掉,而後將新的 segment file 寫入磁盤,這裏會寫一個 commit point ,標識全部新的 segment file ,而後打開 segment file 供搜索使用,同時刪除舊的 segment file 。

# 底層 lucene

簡單來講,lucene 就是一個 jar 包,裏面包含了封裝好的各類創建倒排索引的算法代碼。咱們用 Java 開發的時候,引入 lucene jar,而後基於 lucene 的 api 去開發就能夠了。

經過 lucene,咱們能夠將已有的數據創建索引,lucene 會在本地磁盤上面,給咱們組織索引的數據結構。

# 倒排索引

在搜索引擎中,每一個文檔都有一個對應的文檔 ID,文檔內容被表示爲一系列關鍵詞的集合。例如,文檔 1 通過分詞,提取了 20 個關鍵詞,每一個關鍵詞都會記錄它在文檔中出現的次數和出現位置。

那麼,倒排索引就是關鍵詞到文檔 ID 的映射,每一個關鍵詞都對應着一系列的文件,這些文件中都出現了關鍵詞。

舉個栗子。

有如下文檔:

DocId Doc
1 谷歌地圖之父跳槽 Facebook
2 谷歌地圖之父加盟 Facebook
3 谷歌地圖創始人拉斯離開谷歌加盟 Facebook
4 谷歌地圖之父跳槽 Facebook 與 Wave 項目取消有關
5 谷歌地圖之父拉斯加盟社交網站 Facebook

對文檔進行分詞以後,獲得如下倒排索引

WordId Word DocIds
1 谷歌 1, 2, 3, 4, 5
2 地圖 1, 2, 3, 4, 5
3 之父 1, 2, 4, 5
4 跳槽 1, 4
5 Facebook 1, 2, 3, 4, 5
6 加盟 2, 3, 5
7 創始人 3
8 拉斯 3, 5
9 離開 3
10 4
.. .. ..

另外,實用的倒排索引還能夠記錄更多的信息,好比文檔頻率信息,表示在文檔集合中有多少個文檔包含某個單詞。

那麼,有了倒排索引,搜索引擎能夠很方便地響應用戶的查詢。好比用戶輸入查詢 Facebook ,搜索系統查找倒排索引,從中讀出包含這個單詞的文檔,這些文檔就是提供給用戶的搜索結果。

要注意倒排索引的兩個重要細節:

  • 倒排索引中的全部詞項對應一個或多個文檔;

  • 倒排索引中的詞項根據字典順序升序排列

上面只是一個簡單的栗子,並無嚴格按照字典順序升序排列。

# elasticsearch 的倒排索引是什麼

面試官:想了解你對基礎概念的認知。

解答:通俗解釋一下就能夠。

傳統的咱們的檢索是經過文章,逐個遍歷找到對應關鍵詞的位置。

而倒排索引,是經過分詞策略,造成了詞和文章的映射關係表,這種詞典+映射表即爲倒排索引。有了倒排索引,就能實現 o(1)時間複雜度的效率檢索文章了,極大的提升了檢索效率。

學術的解答方式:

倒排索引,相反於一篇文章包含了哪些詞,它從詞出發,記載了這個詞在哪些文檔中出現過,由兩部分組成——詞典和倒排表。

加分項:倒排索引的底層實現是基於:FST(Finite State Transducer)數據結構。

lucene 從 4+版本後開始大量使用的數據結構是 FST。FST 有兩個優勢:

(1)空間佔用小。經過對詞典中單詞前綴和後綴的重複利用,壓縮了存儲空間;

(2)查詢速度快。O(len(str))的查詢時間複雜度。

# 三、elasticsearch 索引數據多了怎麼辦,如何調優,部署

面試官:想了解大數據量的運維能力。

解答:索引數據的規劃,應在前期作好規劃,正所謂「設計先行,編碼在後」,這樣纔能有效的避免突如其來的數據激增致使集羣處理能力不足引起的線上客戶檢索或者其餘業務受到影響。

如何調優,正如問題 1 所說,這裏細化一下:

3.1 動態索引層面

基於模板+時間+rollover api 滾動建立索引,舉例:設計階段定義:blog 索引的模板格式爲:blogindex時間戳的形式,天天遞增數據。這樣作的好處:不至於數據量激增致使單個索引數據量很是大,接近於上線 2 的 32 次冪-1,索引存儲達到了 TB+甚至更大。

一旦單個索引很大,存儲等各類風險也隨之而來,因此要提早考慮+及早避免。

3.2 存儲層面

冷熱數據分離存儲,熱數據(好比最近 3 天或者一週的數據),其他爲冷數據。

對於冷數據不會再寫入新數據,能夠考慮按期 force_merge 加 shrink 壓縮操做,節省存儲空間和檢索效率。

3.3 部署層面

一旦以前沒有規劃,這裏就屬於應急策略。

結合 ES 自身的支持動態擴展的特色,動態新增機器的方式能夠緩解集羣壓力,注意:若是以前主節點等規劃合理,不須要重啓集羣也能完成動態新增的。

# 四、elasticsearch 是如何實現 master 選舉的

面試官:想了解 ES 集羣的底層原理,再也不只關注業務層面了。

解答:

前置前提:

(1)只有候選主節點(master:true)的節點才能成爲主節點。

(2)最小主節點數(min_master_nodes)的目的是防止腦裂。

覈對了一下代碼,核心入口爲 findMaster,選擇主節點成功返回對應 Master,不然返回 null。選舉流程大體描述以下:

第一步:確認候選主節點數達標,elasticsearch.yml 設置的值

discovery.zen.minimum_master_nodes;

第二步:比較:先斷定是否具有 master 資格,具有候選主節點資格的優先返回;

若兩節點都爲候選主節點,則 id 小的值會主節點。注意這裏的 id 爲 string 類型。

題外話:獲取節點 id 的方法。

# 詳細描述一下 Elasticsearch 索引文檔的過程

面試官:想了解 ES 的底層原理,再也不只關注業務層面了。

解答:

這裏的索引文檔應該理解爲文檔寫入 ES,建立索引的過程。

文檔寫入包含:單文檔寫入和批量 bulk 寫入,這裏只解釋一下:單文檔寫入流程。

記住官方文檔中的這個圖。

第一步:客戶寫集羣某節點寫入數據,發送請求。(若是沒有指定路由/協調節點,請求的節點扮演路由節點的角色。)

第二步:節點 1 接受到請求後,使用文檔_id 來肯定文檔屬於分片 0。請求會被轉到另外的節點,假定節點 3。所以分片 0 的主分片分配到節點 3 上。

第三步:節點 3 在主分片上執行寫操做,若是成功,則將請求並行轉發到節點 1 和節點 2 的副本分片上,等待結果返回。全部的副本分片都報告成功,節點 3 將向協調節點(節點 1)報告成功,節點 1 向請求客戶端報告寫入成功。

若是面試官再問:第二步中的文檔獲取分片的過程?

回答:藉助路由算法獲取,路由算法就是根據路由和文檔 id 計算目標的分片 id 的過程。

# 詳細描述一下 Elasticsearch 搜索的過程?

面試官:想了解 ES 搜索的底層原理,再也不只關注業務層面了。

解答:

搜索拆解爲「query then fetch」 兩個階段。

query 階段的目的:定位到位置,但不取。

步驟拆解以下:

(1)假設一個索引數據有 5 主+1 副本 共 10 分片,一次請求會命中(主或者副本分片中)的一個。

(2)每一個分片在本地進行查詢,結果返回到本地有序的優先隊列中。

(3)第 2)步驟的結果發送到協調節點,協調節點產生一個全局的排序列表。

fetch 階段的目的:取數據。

路由節點獲取全部文檔,返回給客戶端。

# Elasticsearch 在部署時,對 Linux 的設置有哪些優化方法

面試官:想了解對 ES 集羣的運維能力。

解答:

(1)關閉緩存 swap;

(2)堆內存設置爲:Min(節點內存/2, 32GB);

(3)設置最大文件句柄數;

(4)線程池+隊列大小根據業務須要作調整;

(5)磁盤存儲 raid 方式——存儲有條件使用 RAID10,增長單節點性能以及避免單節點存儲故障。

# lucence 內部結構是什麼?

面試官:想了解你的知識面的廣度和深度。

解答:

Lucene 是有索引和搜索的兩個過程,包含索引建立,索引,搜索三個要點。能夠基於這個脈絡展開一些。

# Elasticsearch 是如何實現 Master 選舉的?

(1)Elasticsearch 的選主是 ZenDiscovery 模塊負責的,主要包含 Ping(節點之間經過這個 RPC 來發現彼此)和 Unicast(單播模塊包含一個主機列表以控制哪些節點須要 ping 通)這兩部分;

(2)對全部能夠成爲 master 的節點(node.master: true)根據 nodeId 字典排序,每次選舉每一個節點都把本身所知道節點排一次序,而後選出第一個(第 0 位)節點,暫且認爲它是 master 節點。

(3)若是對某個節點的投票數達到必定的值(能夠成爲 master 節點數 n/2+1)而且該節點本身也選舉本身,那這個節點就是 master。不然從新選舉一直到知足上述條件。

(4)補充:master 節點的職責主要包括集羣、節點和索引的管理,不負責文檔級別的管理;data 節點能夠關閉 http 功能*。

# 十、Elasticsearch 中的節點(好比共 20 個),其中的 10 個

選了一個 master,另外 10 個選了另外一個 master,怎麼辦?

(1)當集羣 master 候選數量不小於 3 個時,能夠經過設置最少投票經過數量(discovery.zen.minimum_master_nodes)超過全部候選節點一半以上來解決腦裂問題;

(3)當候選數量爲兩個時,只能修改成惟一的一個 master 候選,其餘做爲 data 節點,避免腦裂問題。

# 客戶端在和集羣鏈接時,如何選擇特定的節點執行請求的?

TransportClient 利用 transport 模塊遠程鏈接一個 elasticsearch 集羣。它並不加入到集羣中,只是簡單的得到一個或者多個初始化的 transport 地址,並以 輪詢 的方式與這些地址進行通訊。

# 詳細描述一下 Elasticsearch 索引文檔的過程。

協調節點默認使用文檔 ID 參與計算(也支持經過 routing),以便爲路由提供合適的分片。

(1)當分片所在的節點接收到來自協調節點的請求後,會將請求寫入到 MemoryBuffer,而後定時(默認是每隔 1 秒)寫入到 Filesystem Cache,這個從 MomeryBuffer 到 Filesystem Cache 的過程就叫作 refresh;

(2)固然在某些狀況下,存在 Momery Buffer 和 Filesystem Cache 的數據可能會丟失,ES 是經過 translog 的機制來保證數據的可靠性的。其實現機制是接收到請求後,同時也會寫入到 translog 中 ,當 Filesystem cache 中的數據寫入到磁盤中時,纔會清除掉,這個過程叫作 flush;

(3)在 flush 過程當中,內存中的緩衝將被清除,內容被寫入一個新段,段的 fsync 將建立一個新的提交點,並將內容刷新到磁盤,舊的 translog 將被刪除並開始一個新的 translog。

(4)flush 觸發的時機是定時觸發(默認 30 分鐘)或者 translog 變得太大(默認爲 512M)時;

補充:關於 Lucene 的 Segement:

(1)Lucene 索引是由多個段組成,段自己是一個功能齊全的倒排索引。

(2)段是不可變的,容許 Lucene 將新的文檔增量地添加到索引中,而不用從頭重建索引。

(3)對於每個搜索請求而言,索引中的全部段都會被搜索,而且每一個段會消耗 CPU 的時鐘周、文件句柄和內存。這意味着段的數量越多,搜索性能會越低。

(4)爲了解決這個問題,Elasticsearch 會合並小段到一個較大的段,提交新的合併段到磁盤,並刪除那些舊的小段。

# 詳細描述一下 Elasticsearch 更新和刪除文檔的過程。

(1)刪除和更新也都是寫操做,可是 Elasticsearch 中的文檔是不可變的,所以不能被刪除或者改動以展現其變動;

(2)磁盤上的每一個段都有一個相應的.del 文件。當刪除請求發送後,文檔並無真的被刪除,而是在.del 文件中被標記爲刪除。該文檔依然能匹配查詢,可是會在結果中被過濾掉。當段合併時,在.del 文件中被標記爲刪除的文檔將不會被寫入新段。

(3)在新的文檔被建立時,Elasticsearch 會爲該文檔指定一個版本號,當執行更新時,舊版本的文檔在.del 文件中被標記爲刪除,新版本的文檔被索引到一個新段。舊版本的文檔依然能匹配查詢,可是會在結果中被過濾掉。

# 詳細描述一下 Elasticsearch 搜索的過程。

(1)搜索被執行成一個兩階段過程,咱們稱之爲 Query Then Fetch;

(2)在初始查詢階段時,查詢會廣播到索引中每個分片拷貝(主分片或者副本分片)。每一個分片在本地執行搜索並構建一個匹配文檔的大小爲 from + size 的優先隊列。

PS:在搜索的時候是會查詢 Filesystem Cache 的,可是有部分數據還在 MemoryBuffer,因此搜索是近實時的。

(3)每一個分片返回各自優先隊列中 全部文檔的 ID 和排序值 給協調節點,它合併這些值到本身的優先隊列中來產生一個全局排序後的結果列表。

(4)接下來就是 取回階段,協調節點辨別出哪些文檔須要被取回並向相關的分片提交多個 GET 請求。每一個分片加載並 豐 富 文檔,若是有須要的話,接着返回文檔給協調節點。一旦全部的文檔都被取回了,協調節點返回結果給客戶端。

(5)補充:Query Then Fetch 的搜索類型在文檔相關性打分的時候參考的是本分片的數據,這樣在文檔數量較少的時候可能不夠準確,DFS Query Then Fetch 增長了一個預查詢的處理,詢問 Term 和 Document frequency,這個評分更準確,可是性能會變差。*

# 在 Elasticsearch 中,是怎麼根據一個詞找到對應的倒排索引的?

(1)Lucene 的索引過程,就是按照全文檢索的基本過程,將倒排表寫成此文件格式的過程。

(2)Lucene 的搜索過程,就是按照此文件格式將索引進去的信息讀出來,而後計算每篇文檔打分(score)的過程。

# Elasticsearch 在部署時,對 Linux 的設置有哪些優化方法?

(1)64 GB 內存的機器是很是理想的, 可是 32 GB 和 16 GB 機器也是很常見的。少於 8 GB 會拔苗助長。

(2)若是你要在更快的 CPUs 和更多的核心之間選擇,選擇更多的核心更好。多個內核提供的額外併發遠賽過稍微快一點點的時鐘頻率。

(3)若是你負擔得起 SSD,它將遠遠超出任何旋轉介質。基於 SSD 的節點,查詢和索引性能都有提高。若是你負擔得起,SSD 是一個好的選擇。

(4)即便數據中心們近在咫尺,也要避免集羣跨越多個數據中心。絕對要避免集羣跨越大的地理距離。

(5)請確保運行你應用程序的 JVM 和服務器的 JVM 是徹底同樣的。在 Elasticsearch 的幾個地方,使用 Java 的本地序列化。

(6)經過設置 gateway.recover_after_nodes、gateway.expected_nodes、gateway.recover_after_time 能夠在集羣重啓的時候避免過多的分片交換,這可能會讓數據恢復從數個小時縮短爲幾秒鐘。

(7)Elasticsearch 默認被配置爲使用單播發現,以防止節點無心中加入集羣。只有在同一臺機器上運行的節點纔會自動組成集羣。最好使用單播代替組播。

(8)不要隨意修改垃圾回收器(CMS)和各個線程池的大小。

(9)把你的內存的(少於)一半給 Lucene(但不要超過 32 GB!),經過 ES_HEAP_SIZE 環境變量設置。

(10)內存交換到磁盤對服務器性能來講是致命的。若是內存交換到磁盤上,一個 100 微秒的操做可能變成 10 毫秒。再想一想那麼多 10 微秒的操做時延累加起來。不難看出 swapping 對於性能是多麼可怕。

(11)Lucene 使用了大 量 的文件。同時,Elasticsearch 在節點和 HTTP 客戶端之間進行通訊也使用了大量的套接字。全部這一切都須要足夠的文件描述符。你應該增長你的文件描述符,設置一個很大的值,如 64,000。

補充:索引階段性能提高方法

(1)使用批量請求並調整其大小:每次批量數據 5–15 MB 大是個不錯的起始點。

(2)存儲:使用 SSD

(3)段和合並:Elasticsearch 默認值是 20 MB/s,對機械磁盤應該是個不錯的設置。若是你用的是 SSD,能夠考慮提升到 100–200 MB/s。若是你在作批量導入,徹底不在乎搜索,你能夠完全關掉合併限流。另外還能夠增長 index.translog.flush_threshold_size 設置,從默認的 512 MB 到更大一些的值,好比 1 GB,這能夠在一次清空觸發的時候在事務日誌裏積累出更大的段。

(4)若是你的搜索結果不須要近實時的準確度,考慮把每一個索引的 index.refresh_interval 改到 30s。

(5)若是你在作大批量導入,考慮經過設置 index.number_of_replicas: 0 關閉副本。

# 對於 GC 方面,在使用 Elasticsearch 時要注意什麼?

(1)倒排詞典的索引須要常駐內存,沒法 GC,須要監控 data node 上 segmentmemory 增加趨勢。

(2)各種緩存,field cache, filter cache, indexing cache, bulk queue 等等,要設置合理的大小,而且要應該根據最壞的狀況來看 heap 是否夠用,也就是各種緩存所有佔滿的時候,還有 heap 空間能夠分配給其餘任務嗎?避免採用 clear cache 等「自欺欺人」的方式來釋放內存。

(3)避免返回大量結果集的搜索與聚合。確實須要大量拉取數據的場景,能夠採用 scan & scroll api 來實現。

(4)cluster stats 駐留內存並沒有法水平擴展,超大規模集羣能夠考慮分拆成多個集羣經過 tribe node 鏈接。

(5)想知道 heap 夠不夠,必須結合實際應用場景,並對集羣的 heap 使用狀況作持續的監控。

(6)根據監控數據理解內存需求,合理配置各種 circuit breaker,將內存溢出風險下降到最低

# 1八、Elasticsearch 對於大數據量(上億量級)的聚合如何實現?

Elasticsearch 提供的首個近似聚合是 cardinality 度量。它提供一個字段的基數,即該字段的 distinct 或者 unique 值的數目。它是基於 HLL 算法的。HLL 會先對咱們的輸入做哈希運算,而後根據哈希運算的結果中的 bits 作機率估算從而獲得基數。其特色是:可配置的精度,用來控制內存的使用(更精確 = 更多內存);小的數據集精度是很是高的;咱們能夠經過配置參數,來設置去重須要的固定內存使用量。不管數千仍是數十億的惟一值,內存使用量只與你配置的精確度相關。

# 1九、在併發狀況下,Elasticsearch 若是保證讀寫一致?

(1)能夠經過版本號使用樂觀併發控制,以確保新版本不會被舊版本覆蓋,由應用層來處理具體的衝突;

(2)另外對於寫操做,一致性級別支持 quorum/one/all,默認爲 quorum,即只有當大多數分片可用時才容許寫操做。但即便大多數可用,也可能存在由於網絡等緣由致使寫入副本失敗,這樣該副本被認爲故障,分片將會在一個不一樣的節點上重建。

(3)對於讀操做,能夠設置 replication 爲 sync(默認),這使得操做在主分片和副本分片都完成後纔會返回;若是設置 replication 爲 async 時,也能夠經過設置搜索請求參數_preference 爲 primary 來查詢主分片,確保文檔是最新版本。

# 20、如何監控 Elasticsearch 集羣狀態?

Marvel 讓你能夠很簡單的經過 Kibana 監控 Elasticsearch。你能夠實時查看你的集羣健康狀態和性能,也能夠分析過去的集羣、索引和節點指標。

# 2一、介紹下大家電商搜索的總體技術架構。

# 介紹一下大家的個性化搜索方案?

基於 word2vec 和 Elasticsearch 實現個性化搜索

(1)基於 word2vec、Elasticsearch 和自定義的腳本插件,咱們就實現了一個個性化的搜索服務,相對於原有的實現,新版的點擊率和轉化率都有大幅的提高;

(2)基於 word2vec 的商品向量還有一個可用之處,就是能夠用來實現類似商品的推薦;

(3)使用 word2vec 來實現個性化搜索或個性化推薦是有必定侷限性的,由於它只能處理用戶點擊歷史這樣的時序數據,而沒法全面的去考慮用戶偏好,這個仍是有很大的改進和提高的空間;

# 是否瞭解字典樹?

經常使用字典數據結構以下所示:

Trie 的核心思想是空間換時間,利用字符串的公共前綴來下降查詢時間的開銷以達到提升效率的目的。它有 3 個基本性質:

1)根節點不包含字符,除根節點外每個節點都只包含一個字符。

2)從根節點到某一節點,路徑上通過的字符鏈接起來,爲該節點對應的字符串。

3)每一個節點的全部子節點包含的字符都不相同。

(1)能夠看到,trie 樹每一層的節點數是 26^i 級別的。因此爲了節省空間,咱們還能夠用動態鏈表,或者用數組來模擬動態。而空間的花費,不會超過單詞數 × 單詞長度。

(2)實現:對每一個結點開一個字母集大小的數組,每一個結點掛一個鏈表,使用左兒子右兄弟表示法記錄這棵樹;

(3)對於中文的字典樹,每一個節點的子節點用一個哈希表存儲,這樣就不用浪費太大的空間,並且查詢速度上能夠保留哈希的複雜度 O(1)。

# 拼寫糾錯是如何實現的?

(1)拼寫糾錯是基於編輯距離來實現;編輯距離是一種標準的方法,它用來表示通過插入、刪除和替換操做從一個字符串轉換到另一個字符串的最小操做步數;

(2)編輯距離的計算過程:好比要計算 batyu 和 beauty 的編輯距離,先建立一個 7×8 的表(batyu 長度爲 5,coffee 長度爲 6,各加 2),接着,在以下位置填入黑色數字。其餘格的計算過程是取如下三個值的最小值:

若是最上方的字符等於最左方的字符,則爲左上方的數字。不然爲左上方的數字+1。(對於 3,3 來講爲 0)

左方數字+1(對於 3,3 格來講爲 2)

上方數字+1(對於 3,3 格來講爲 2)

最終取右下角的值即爲編輯距離的值 3。

對於拼寫糾錯,咱們考慮構造一個度量空間(Metric Space),該空間內任何關係知足如下三條基本條件:

d(x,y) = 0 -- 假如 x 與 y 的距離爲 0,則 x=y

d(x,y) = d(y,x) -- x 到 y 的距離等同於 y 到 x 的距離

d(x,y) + d(y,z) >= d(x,z) -- 三角不等式

(1)根據三角不等式,則知足與 query 距離在 n 範圍內的另外一個字符轉 B,其與 A 的距離最大爲 d+n,最小爲 d-n。

(2)BK 樹的構造就過程以下:每一個節點有任意個子節點,每條邊有個值表示編輯距離。全部子節點到父節點的邊上標註 n 表示編輯距離剛好爲 n。好比,咱們有棵樹父節點是」book」和兩個子節點」cake」和」books」,」book」到」books」的邊標號 1,」book」到」cake」的邊上標號 4。從字典裏構造好樹後,不管什麼時候你想插入新單詞時,計算該單詞與根節點的編輯距離,而且查找數值爲 d(neweord, root)的邊。遞歸得與各子節點進行比較,直到沒有子節點,你就能夠建立新的子節點並將新單詞保存在那。好比,插入」boo」到剛纔上述例子的樹中,咱們先檢查根節點,查找 d(「book」, 「boo」) = 1 的邊,而後檢查標號爲 1 的邊的子節點,獲得單詞」books」。咱們再計算距離 d(「books」, 「boo」)=2,則將新單詞插在」books」以後,邊標號爲 2。

(3)查詢類似詞以下:計算單詞與根節點的編輯距離 d,而後遞歸查找每一個子節點標號爲 d-n 到 d+n(包含)的邊。假如被檢查的節點與搜索單詞的距離 d 小於 n,則返回該節點並繼續查詢。好比輸入 cape 且最大容忍距離爲 1,則先計算和根的編輯距離 d(「book」, 「cape」)=4,而後接着找和根節點之間編輯距離爲 3 到 5 的,這個就找到了 cake 這個節點,計算 d(「cake」, 「cape」)=1,知足條件因此返回 cake,而後再找和 cake 節點編輯距離是 0 到 2 的,分別找到 cape 和 cart 節點,這樣就獲得 cape 這個知足條件的結果。

·END·


若是您喜歡本文,歡迎點擊右上角,把文章分享到朋友圈~~

做者:dunwu

來源:dunwu.github.io/db-tutorial/nosql/elasticsearch/elasticsearch-interview.html

版權申明:內容來源網絡,版權歸原創者全部。除非沒法確認,咱們都會標明做者及出處,若有侵權煩請告知,咱們會當即刪除並表示歉意。謝謝!

IT技術分享社區

我的博客網站:https://programmerblog.xyz

文章推薦程序員效率:畫流程圖經常使用的工具程序員效率:整理經常使用的在線筆記軟件遠程辦公:經常使用的遠程協助軟件,你都知道嗎?51單片機程序下載、ISP及串口基礎知識硬件:斷路器、接觸器、繼電器基礎知識

相關文章
相關標籤/搜索