#ElasticSearch深入:搜索性能調優

性能優化的殺手鐗—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 秒才能查出來一頁數據了。

有什麼解決方案嗎?

跟產品經理說,你係統不允許翻那麼深的頁,默認翻的越深,性能就越差。

類似於微博中,下拉刷微博,刷出來一頁一頁的,你可以用 scroll api ,關於如何使用,自行上網搜索。

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

但是,唯一的一點就是,這個適合於那種類似微博下拉翻頁的,不能隨意跳到任何一頁的場景。也就是說,你不能先進入第 10 頁,然後去第 120 頁,然後又回到第 58 頁,不能隨意亂跳頁。所以現在很多產品,都是不允許你隨意翻頁的,app,也有一些網站,做的就是你只能往下拉,一頁一頁的翻。

初始化時必須指定 scroll 參數,告訴 es 要保存此次搜索的上下文多長時間。你需要確保用戶不會持續不斷翻頁翻幾個小時,否則可能因爲超時而失敗。

除了用 scroll api ,你也可以用 search_after 來做, search_after 的思想是使用前一頁的結果來幫助檢索下一頁的數據,顯然,這種方式也不允許你隨意翻頁,你只能一頁頁往後翻。初始化時,需要使用一個唯一值的字段作爲 sort 字段。

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