ElasticSearch在數十億級別資料下,如何提高查詢效率?

lihong發表於2019-04-28

面試題

es 在資料量很大的情況下(數十億級別)如何提高查詢效率啊?

面試官心理分析

這個問題是肯定要問的,說白了,就是看你有沒有實際幹過 es,因為啥?其實 es 效能並沒有你想象中那麼好的。很多時候資料量大了,特別是有幾億條資料的時候,可能你會懵逼的發現,跑個搜尋怎麼一下  5~10s ,坑爹了。第一次搜尋的時候,是 5~10s ,後面反而就快了,可能就幾百毫秒。

你就很懵,每個使用者第一次訪問都會比較慢,比較卡麼?所以你要是沒玩兒過 es,或者就是自己玩玩兒 demo,被問到這個問題容易懵逼,顯示出你對 es 確實玩兒的不怎麼樣?

面試題剖析

說實話,es 效能最佳化是沒有什麼銀彈的,啥意思呢?就是 不要期待著隨手調一個引數,就可以萬能的應對所有的效能慢的場景 。也許有的場景是你換個引數,或者調整一下語法,就可以搞定,但是絕對不是所有場景都可以這樣。

效能最佳化的殺手鐧——filesystem cache

你往 es 裡寫的資料,實際上都寫到磁碟檔案裡去了, 查詢的時候 ,作業系統會將磁碟檔案裡的資料自動快取到  filesystem cache  裡面去。

ElasticSearch在數十億級別資料下,如何提高查詢效率?


es 的搜尋引擎嚴重依賴於底層的  filesystem cache ,你如果給  filesystem cache  更多的記憶體,儘量讓記憶體可以容納所有的  idx segment file  索引資料檔案,那麼你搜尋的時候就基本都是走記憶體的,效能會非常高。

效能差距究竟可以有多大?我們之前很多的測試和壓測,如果走磁碟一般肯定上秒,搜尋效能絕對是秒級別的,1秒、5秒、10秒。但如果是走  filesystem cache ,是走純記憶體的,那麼一般來說效能比走磁碟要高一個數量級,基本上就是毫秒級的,從幾毫秒到幾百毫秒不等。

這裡有個真實的案例。某個公司 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 效能要好,最佳的情況下,就是你的機器的記憶體,至少可以容納你的總資料量的一半。

根據我們自己的生產環境實踐經驗,最佳的情況下,是僅僅在 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 欄位。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69917606/viewspace-2642809/,如需轉載,請註明出處,否則將追究法律責任。

相關文章