分佈式搜索引擎-ES在數據量很大的情況下(數十億級別)如何提高查詢性能?

分佈式搜索引擎的面試連環炮

面試題

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

面試官心理分析

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

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

面試題剖析

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

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

一塊一塊來分析吧

在這個海量數據的場景下,如何提升es搜索的性能,也是我們之前生產環境實踐經驗所得

(1)性能優化的殺手鐗——filesystem cache

os cache,操作系統的緩存

你往es裏寫的數據,實際上都寫到磁盤文件裏去了,磁盤文件裏的數據操作系統會自動將裏面的數據緩存到os cache裏面去

es的搜索引擎嚴重依賴於底層的filesystem cache,你如果給filesystem cache更多的內存,儘量讓內存可以容納所有的indx 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內存

我就問他,ok,那麼就是你往es集羣裏寫入的數據有多少數據量?

如果你此時,你整個,磁盤上索引數據文件,在3臺機器上,一共佔用了1T的磁盤容量,你的es數據量是1t,每臺機器的數據量是300g

你覺得你的性能能好嗎?filesystem cache的內存才100g,十分之一的數據可以放內存,其他的都在磁盤,然後你執行搜索操作,大部分操作都是走磁盤,性能肯定差

當時他們的情況就是這樣子,es在測試,弄了3臺機器,自己覺得還不錯,64G內存的物理機。自以爲可以容納1T的數據量。

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

比如說,你一共要在es中存儲1T的數據,那麼你的多臺機器留個filesystem cache的內存加起來綜合,至少要到512G,至少半數的情況下,搜索是走內存的,性能一般可以到幾秒鐘,2秒,3秒,5秒

如果最佳的情況下,我們自己的生產環境實踐經驗,所以說我們當時的策略,是僅僅在es中就存少量的數據,就是你要用來搜索的那些索引,內存留給filesystem cache的,就100G,那麼你就控制在100gb以內,相當於是,你的數據幾乎全部走內存來搜索,性能非常之高,一般可以在1秒以內

比如說你現在有一行數據

id name age ....30個字段

但是你現在搜索,只需要根據id name age三個字段來搜索

如果你傻乎乎的往es裏寫入一行數據所有的字段,就會導致說70%的數據是不用來搜索的,結果硬是佔據了es機器上的filesystem cache的空間,單挑數據的數據量越大,就會導致filesystem cahce能緩存的數據就越少

僅僅只是寫入es中要用來檢索的少數幾個字段就可以了,比如說,就寫入es id name age三個字段就可以了,然後你可以把其他的字段數據存在mysql裏面,我們一般是建議用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~10秒,現在可能性能就會很高,每次查詢就是50ms。

elastcisearch減少數據量僅僅放要用於搜索的幾個關鍵字段即可,儘量寫入es的數據量跟es機器的filesystem cache是差不多的就可以了;其他不用來檢索的數據放hbase裏,或者mysql。

所以之前有些學員也是問,我也是跟他們說,儘量在es裏,就存儲必須用來搜索的數據,比如說你現在有一份數據,有100個字段,其實用來搜索的只有10個字段,建議是將10個字段的數據,存入es,剩下90個字段的數據,可以放mysql,hadoop hbase,都可以

這樣的話,es數據量很少,10個字段的數據,都可以放內存,就用來搜索,搜索出來一些id,通過id去mysql,hbase裏面去查詢明細的數據

(2)數據預熱

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

舉個例子,就比如說,微博,你可以把一些大v,平時看的人很多的數據給提前你自己後臺搞個系統,每隔一會兒,你自己的後臺系統去搜索一下熱數據,刷到filesystem cache裏去,後面用戶實際上來看這個熱數據的時候,他們就是直接從內存裏搜索了,很快。

電商,你可以將平時查看最多的一些商品,比如說iphone 8,熱數據提前後臺搞個程序,每隔1分鐘自己主動訪問一次,刷到filesystem cache裏去。

對於那些你覺得比較熱的,經常會有人訪問的數據,最好做一個專門的緩存預熱子系統,就是對熱數據,每隔一段時間,你就提前訪問一下,讓數據進入filesystem cache裏面去。這樣期待下次別人訪問的時候,一定性能會好一些。

(3)冷熱分離

關於es性能優化,數據拆分,我之前說將大量不搜索的字段,拆分到別的存儲中去,這個就是類似於後面我最後要講的mysql分庫分表的垂直拆分。

es可以做類似於mysql的水平拆分,就是說將大量的訪問很少,頻率很低的數據,單獨寫一個索引,然後將訪問很頻繁的熱數據單獨寫一個索引

你最好是將冷數據寫入一個索引中,然後熱數據寫入另外一個索引中,這樣可以確保熱數據在被預熱之後,儘量都讓他們留在filesystem os cache裏,別讓冷數據給沖刷掉。

你看,假設你有6臺機器,2個索引,一個放冷數據,一個放熱數據,每個索引3個shard

3臺機器放熱數據index;另外3臺機器放冷數據index

然後這樣的話,你大量的時候是在訪問熱數據index,熱數據可能就佔總數據量的10%,此時數據量很少,幾乎全都保留在filesystem cache裏面了,就可以確保熱數據的訪問性能是很高的。

但是對於冷數據而言,是在別的index裏的,跟熱數據index都不再相同的機器上,大家互相之間都沒什麼聯繫了。如果有人訪問冷數據,可能大量數據是在磁盤上的,此時性能差點,就10%的人去訪問冷數據;90%的人在訪問熱數據。

(4)document模型設計

有不少同學問我,mysql,有兩張表

訂單表:id order_code total_price

1 測試訂單 5000

訂單條目表:id order_id goods_id purchase_count price

1 1 1 2 2000

2 1 2 5 200

我在mysql裏,都是select * from order join order_item on order.id=order_item.order_id where order.id=1

1 測試訂單 5000 1 1 1 2 2000

1 測試訂單 5000 2 1 2 5 200

在es裏該怎麼玩兒,es裏面的複雜的關聯查詢,複雜的查詢語法,儘量別用,一旦用了性能一般都不太好

設計es裏的數據模型

寫入es的時候,搞成兩個索引,order索引,orderItem索引

order索引,裏面就包含id order_code total_price

orderItem索引,裏面寫入進去的時候,就完成join操作,id order_code total_price id order_id goods_id purchase_count price

寫入es的java系統裏,就完成關聯,將關聯好的數據直接寫入es中,搜索的時候,就不需要利用es的搜索語法去完成join來搜索了

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

很多同學在問我,很多複雜的亂七八糟的一些操作,如何執行

兩個思路,在搜索/查詢的時候,要執行一些業務強相關的特別複雜的操作:

1)在寫入數據的時候,就設計好模型,加幾個字段,把處理好的數據寫入加的字段裏面

2)自己用java程序封裝,es能做的,用es來做,搜索出來的數據,在java程序裏面去做,比如說我們,基於es,用java封裝一些特別複雜的操作

(5)分頁性能優化

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秒才能查出來一頁數據了

1)不允許深度分頁/默認深度分頁性能很慘

你係統不允許他翻那麼深的頁,pm,默認翻的越深,性能就越差

2)類似於app裏的推薦商品不斷下拉出來一頁一頁的

類似於微博中,下拉刷微博,刷出來一頁一頁的,你可以用scroll api,自己百度

scroll會一次性給你生成所有數據的一個快照,然後每次翻頁就是通過遊標移動,獲取下一頁下一頁這樣子,性能會比上面說的那種分頁性能也高很多很多

針對這個問題,你可以考慮用scroll來進行處理,scroll的原理實際上是保留一個數據快照,然後在一定時間內,你如果不斷的滑動往後翻頁的時候,類似於你現在在瀏覽微博,不斷往下刷新翻頁。那麼就用scroll不斷通過遊標獲取下一頁數據,這個性能是很高的,比es實際翻頁要好的多的多。

但是唯一的一點就是,這個適合於那種類似微博下拉翻頁的,不能隨意跳到任何一頁的場景。同時這個scroll是要保留一段時間內的數據快照的,你需要確保用戶不會持續不斷翻頁翻幾個小時。

無論多少頁,性能基本上都是毫秒級的

因爲scroll api是隻能一頁一頁往後翻的,是不能說,先進入第10頁,然後去120頁,回到58頁,不能隨意亂跳頁。所以現在很多產品,都是不允許你隨意翻頁的,app,也有一些網站,做的就是你只能往下拉,一頁一頁的翻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章