2019-06-27

1 面試題

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

2 考點(diǎn)分析

就是考查你是否實(shí)操過ES.
因?yàn)镋S其實(shí)性能并沒有想象中那么好.很多時候數(shù)據(jù)量過大,特別是有幾億條數(shù)據(jù)的時候,可能你會懵逼的發(fā)現(xiàn),跑個搜索怎么一下5秒~10秒,慢死了! 第一次搜索的時候,是5~10秒,后面反而就快了,可能就幾百毫秒。

你就很懵,每個用戶第一次訪問都會比較慢,比較卡么?

所以你要是沒玩過ES,或者只是自己玩玩demo,被問到這個問題容易懵逼,顯示出你對ES確實(shí)不怎么樣

3 詳解

ES性能優(yōu)化是沒有銀彈的,不要指望著隨手調(diào)幾個參數(shù),就可應(yīng)對所有性能低下的場景.

在海量數(shù)據(jù)場景下,如何提升ES搜索的性能,也是我們寶貴的生產(chǎn)環(huán)境的實(shí)踐經(jīng)驗(yàn)

3.1 性能優(yōu)化殺手锏 — filesystem cache

os cache,操作系統(tǒng)的緩存

你往ES寫的數(shù)據(jù),實(shí)際上都寫到了磁盤文件,操作系統(tǒng)會自動將磁盤文件里的數(shù)據(jù)緩存到os cache.


ES嚴(yán)重依賴于底層的filesystem cache,你如果給filesystem cache更多的內(nèi)存,盡量讓內(nèi)存可以容納所有的indx segment file索引數(shù)據(jù)文件,那么你搜索的時候就基本都是走內(nèi)存的,性能會非常高.

性能差距可以有多大?

  • 如果走磁盤一般肯定上秒,搜索性能絕對是秒級別的,1秒,5秒,10秒
  • 但是如果是走filesystem cache,純內(nèi)存,那么一般來說性能比磁盤要高一個數(shù)量級,基本毫秒級,從幾毫秒到幾百毫秒不等.

真實(shí)案例

ES節(jié)點(diǎn)有3個,每個節(jié)點(diǎn),看起來內(nèi)存很多-64G,總內(nèi)存192G.

每個節(jié)點(diǎn)給ES JVM Heap是32G,那么剩下來留給filesystem cache的就是每個節(jié)點(diǎn)僅32G,集群的filesystem cache就是32 * 3 = 96G內(nèi)存

我就問他,ok,那么就是你往es集群里寫入的數(shù)據(jù)有多少數(shù)據(jù)量?

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

你覺得你的性能能好嗎?filesystem cache的內(nèi)存才100g,十分之一的數(shù)據(jù)可以放內(nèi)存,其他的都在磁盤,然后你執(zhí)行搜索操作,大部分操作都是走磁盤,性能肯定差

當(dāng)時他們的情況就是這樣子,es在測試,弄了3臺機(jī)器,自己覺得還不錯,64G內(nèi)存的物理機(jī)。自以為可以容納1T的數(shù)據(jù)量。

歸根結(jié)底,你要讓es性能要好,最佳的情況下,就是你的機(jī)器的內(nèi)存,至少可以容納你的總數(shù)據(jù)量的一半

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

如果最佳的情況下,我們自己的生產(chǎn)環(huán)境實(shí)踐經(jīng)驗(yàn),所以說我們當(dāng)時的策略,是僅僅在es中就存少量的數(shù)據(jù),就是你要用來搜索的那些索引,內(nèi)存留給filesystem cache的,就100G,那么你就控制在100gb以內(nèi),相當(dāng)于是,你的數(shù)據(jù)幾乎全部走內(nèi)存來搜索,性能非常之高,一般可以在1秒以內(nèi)

比如說你現(xiàn)在有一行數(shù)據(jù)

id name age ....30個字段

但是你現(xiàn)在搜索,只需要根據(jù)id name age三個字段來搜索

如果你傻乎乎的往es里寫入一行數(shù)據(jù)所有的字段,就會導(dǎo)致說70%的數(shù)據(jù)是不用來搜索的,結(jié)果硬是占據(jù)了es機(jī)器上的filesystem cache的空間,單挑數(shù)據(jù)的數(shù)據(jù)量越大,就會導(dǎo)致filesystem cahce能緩存的數(shù)據(jù)就越少

僅僅只是寫入es中要用來檢索的少數(shù)幾個字段就可以了,比如說,就寫入es id name age三個字段就可以了,然后你可以把其他的字段數(shù)據(jù)存在mysql里面,我們一般是建議用es + hbase的這么一個架構(gòu)。

hbase的特點(diǎn)是適用于海量數(shù)據(jù)的在線存儲,就是對hbase可以寫入海量數(shù)據(jù),不要做復(fù)雜的搜索,就是做很簡單的一些根據(jù)id或者范圍進(jìn)行查詢的這么一個操作就可以了

從es中根據(jù)name和age去搜索,拿到的結(jié)果可能就20個doc id,然后根據(jù)doc id到hbase里去查詢每個doc id對應(yīng)的完整的數(shù)據(jù),給查出來,再返回給前端。

你最好是寫入es的數(shù)據(jù)小于等于,或者是略微大于es的filesystem cache的內(nèi)存容量

然后你從es檢索可能就花費(fèi)20ms,然后再根據(jù)es返回的id去hbase里查詢,查20條數(shù)據(jù),可能也就耗費(fèi)個30ms,可能你原來那么玩兒,1T數(shù)據(jù)都放es,會每次查詢都是5~10秒,現(xiàn)在可能性能就會很高,每次查詢就是50ms。

elastcisearch減少數(shù)據(jù)量僅僅放要用于搜索的幾個關(guān)鍵字段即可,盡量寫入es的數(shù)據(jù)量跟es機(jī)器的filesystem cache是差不多的就可以了;其他不用來檢索的數(shù)據(jù)放hbase里,或者mysql。

所以之前有些學(xué)員也是問,我也是跟他們說,盡量在es里,就存儲必須用來搜索的數(shù)據(jù),比如說你現(xiàn)在有一份數(shù)據(jù),有100個字段,其實(shí)用來搜索的只有10個字段,建議是將10個字段的數(shù)據(jù),存入es,剩下90個字段的數(shù)據(jù),可以放mysql,hadoop hbase,都可以

這樣的話,es數(shù)據(jù)量很少,10個字段的數(shù)據(jù),都可以放內(nèi)存,就用來搜索,搜索出來一些id,通過id去mysql,hbase里面去查詢明細(xì)的數(shù)據(jù)

(2)數(shù)據(jù)預(yù)熱

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

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

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

對于那些你覺得比較熱的,經(jīng)常會有人訪問的數(shù)據(jù),最好做一個專門的緩存預(yù)熱子系統(tǒng),就是對熱數(shù)據(jù),每隔一段時間,你就提前訪問一下,讓數(shù)據(jù)進(jìn)入filesystem cache里面去。這樣期待下次別人訪問的時候,一定性能會好一些。

(3)冷熱分離

關(guān)于es性能優(yōu)化,數(shù)據(jù)拆分,我之前說將大量不搜索的字段,拆分到別的存儲中去,這個就是類似于后面我最后要講的mysql分庫分表的垂直拆分。

es可以做類似于mysql的水平拆分,就是說將大量的訪問很少,頻率很低的數(shù)據(jù),單獨(dú)寫一個索引,然后將訪問很頻繁的熱數(shù)據(jù)單獨(dú)寫一個索引

你最好是將冷數(shù)據(jù)寫入一個索引中,然后熱數(shù)據(jù)寫入另外一個索引中,這樣可以確保熱數(shù)據(jù)在被預(yù)熱之后,盡量都讓他們留在filesystem os cache里,別讓冷數(shù)據(jù)給沖刷掉。

你看,假設(shè)你有6臺機(jī)器,2個索引,一個放冷數(shù)據(jù),一個放熱數(shù)據(jù),每個索引3個shard

3臺機(jī)器放熱數(shù)據(jù)index;另外3臺機(jī)器放冷數(shù)據(jù)index

然后這樣的話,你大量的時候是在訪問熱數(shù)據(jù)index,熱數(shù)據(jù)可能就占總數(shù)據(jù)量的10%,此時數(shù)據(jù)量很少,幾乎全都保留在filesystem cache里面了,就可以確保熱數(shù)據(jù)的訪問性能是很高的。

但是對于冷數(shù)據(jù)而言,是在別的index里的,跟熱數(shù)據(jù)index都不再相同的機(jī)器上,大家互相之間都沒什么聯(lián)系了。如果有人訪問冷數(shù)據(jù),可能大量數(shù)據(jù)是在磁盤上的,此時性能差點(diǎn),就10%的人去訪問冷數(shù)據(jù);90%的人在訪問熱數(shù)據(jù)。

(4)document模型設(shè)計

有不少同學(xué)問我,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里面的復(fù)雜的關(guān)聯(lián)查詢,復(fù)雜的查詢語法,盡量別用,一旦用了性能一般都不太好

設(shè)計es里的數(shù)據(jù)模型

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

order索引,里面就包含id order_code total_price
orderItem索引,里面寫入進(jìn)去的時候,就完成join操作,id order_code total_price id order_id goods_id purchase_count price

寫入es的java系統(tǒng)里,就完成關(guān)聯(lián),將關(guān)聯(lián)好的數(shù)據(jù)直接寫入es中,搜索的時候,就不需要利用es的搜索語法去完成join來搜索了

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

很多同學(xué)在問我,很多復(fù)雜的亂七八糟的一些操作,如何執(zhí)行

兩個思路,在搜索/查詢的時候,要執(zhí)行一些業(yè)務(wù)強(qiáng)相關(guān)的特別復(fù)雜的操作:

1)在寫入數(shù)據(jù)的時候,就設(shè)計好模型,加幾個字段,把處理好的數(shù)據(jù)寫入加的字段里面
2)自己用java程序封裝,es能做的,用es來做,搜索出來的數(shù)據(jù),在java程序里面去做,比如說我們,基于es,用java封裝一些特別復(fù)雜的操作

(5)分頁性能優(yōu)化

es的分頁是較坑的,為啥呢?舉個例子吧,假如你每頁是10條數(shù)據(jù),你現(xiàn)在要查詢第100頁,實(shí)際上是會把每個shard上存儲的前1000條數(shù)據(jù)都查到一個協(xié)調(diào)節(jié)點(diǎn)上,如果你有個5個shard,那么就有5000條數(shù)據(jù),接著協(xié)調(diào)節(jié)點(diǎn)對這5000條數(shù)據(jù)進(jìn)行一些合并、處理,再獲取到最終第100頁的10條數(shù)據(jù)。

分布式的,你要查第100頁的10條數(shù)據(jù),你是不可能說從5個shard,每個shard就查2條數(shù)據(jù)?最后到協(xié)調(diào)節(jié)點(diǎn)合并成10條數(shù)據(jù)?你必須得從每個shard都查1000條數(shù)據(jù)過來,然后根據(jù)你的需求進(jìn)行排序、篩選等等操作,最后再次分頁,拿到里面第100頁的數(shù)據(jù)。

你翻頁的時候,翻的越深,每個shard返回的數(shù)據(jù)就越多,而且協(xié)調(diào)節(jié)點(diǎn)處理的時間越長。非??拥?。所以用es做分頁的時候,你會發(fā)現(xiàn)越翻到后面,就越是慢。

我們之前也是遇到過這個問題,用es作分頁,前幾頁就幾十毫秒,翻到10頁之后,幾十頁的時候,基本上就要5~10秒才能查出來一頁數(shù)據(jù)了

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

你系統(tǒng)不允許他翻那么深的頁,pm,默認(rèn)翻的越深,性能就越差

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

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

scroll會一次性給你生成所有數(shù)據(jù)的一個快照,然后每次翻頁就是通過游標(biāo)移動,獲取下一頁下一頁這樣子,性能會比上面說的那種分頁性能也高很多很多

針對這個問題,你可以考慮用scroll來進(jìn)行處理,scroll的原理實(shí)際上是保留一個數(shù)據(jù)快照,然后在一定時間內(nèi),你如果不斷的滑動往后翻頁的時候,類似于你現(xiàn)在在瀏覽微博,不斷往下刷新翻頁。那么就用scroll不斷通過游標(biāo)獲取下一頁數(shù)據(jù),這個性能是很高的,比es實(shí)際翻頁要好的多的多。

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

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

因?yàn)閟croll api是只能一頁一頁往后翻的,是不能說,先進(jìn)入第10頁,然后去120頁,回到58頁,不能隨意亂跳頁。所以現(xiàn)在很多產(chǎn)品,都是不允許你隨意翻頁的,app,也有一些網(wǎng)站,做的就是你只能往下拉,一頁一頁的翻

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容