經典面試題:Elasticsearch 如何做到億級數據查詢毫秒級返回? 面試題 面試官心理分析 面試題剖析 性能優化的殺手鐧——filesystem cache 數據預熱 冷熱分離 document 模型設計 分頁性能優化 不允許深度分頁(默認深度分頁性能很差) 類似於 app 里的推薦商品不斷下拉出來一頁一頁的 《Netty 實現原理與源碼解析 —— 精品合集》 《Spring 實現原理與源碼解析 —— 精品合集》 《MyBatis 實現原理與源碼解析 —— 精品合集》 《Spring MVC 實現原理與源碼解析 —— 精品合集》 《Spring Boot 實現原理與源碼解析 —— 精品合集》 《資料庫實體設計合集》 《Java 面試題 —— 精品合集》 《Java 學習指南 —— 精品合集》 面試題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 欄位。 來源:http://sina.lt/gfZE 搜索微信號(ID:芋道源碼),可以獲得各種 Java 源碼解析、原理講解、面試題、學習指南。 並且,回復【書籍】後,可以領取筆者推薦的各種 Java 從入門到架構的 100 本書籍。 來吧,騷年~ 推薦閱讀: 相关文章 {{#data}} {{title}} {{/data}}