若是面試的時候碰到這樣一個面試題:ES 在數據量很大的狀況下(數十億級別)如何提升查詢效率?
這個問題說白了,就是看你有沒有實際用過 ES,由於啥?其實 ES 性能並無你想象中那麼好的。前端
不少時候數據量大了,特別是有幾億條數據的時候,可能你會懵逼的發現,跑個搜索怎麼一下 5~10s,坑爹了。面試
第一次搜索的時候,是 5~10s,後面反而就快了,可能就幾百毫秒。緩存
你就很懵,每一個用戶第一次訪問都會比較慢,比較卡麼?因此你要是沒玩兒過 ES,或者就是本身玩玩兒 Demo,被問到這個問題容易懵逼,顯示出你對 ES 確實玩的不怎麼樣?性能優化
說實話,ES 性能優化是沒有銀彈的。啥意思呢?就是不要期待着隨手調一個參數,就能夠萬能的應對全部的性能慢的場景。架構
也許有的場景是你換個參數,或者調整一下語法,就能夠搞定,可是絕對不是全部場景均可以這樣。分佈式
性能優化的殺手鐗:Filesystem Cache性能
你往 ES 裏寫的數據,實際上都寫到磁盤文件裏去了,查詢的時候,操做系統會將磁盤文件裏的數據自動緩存到 Filesystem Cache 裏面去。學習
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 字段。
END
我的公衆號:石杉的架構筆記(ID:shishan100)
歡迎長按下圖關注公衆號:石杉的架構筆記!
公衆號後臺回覆資料,獲取做者獨家祕製學習資料
石杉的架構筆記,BAT架構經驗傾囊相授