記憶體吞金獸(Elasticsearch)的那些事兒 — 常見問題痛點及解決方案

系列目錄

記憶體吞金獸(Elasticsearch)的那些事兒 — 認識一下

記憶體吞金獸(Elasticsearch)的那些事兒 — 數據結構及巧妙演算法

記憶體吞金獸(Elasticsearch)的那些事兒 — 架構&三高保證

記憶體吞金獸(Elasticsearch)的那些事兒 — 寫入&檢索原理

記憶體吞金獸(Elasticsearch)的那些事兒 — 常見問題痛點及解決方案

 

1、大數據量的查詢效率如何保證:

查詢的流程:往 ES 里寫的數據,實際上都寫到磁碟文件里去了,查詢的時候,作業系統會將磁碟文件里的數據自動快取到 Filesystem Cache 裡面去

 

最佳的情況下,就是機器的記憶體,至少可以容納總數據量的一半,僅僅在 es 中就存少量的數據,就是要用來搜索的那些索引,如果記憶體留給 filesystem cache 的是 100G,那麼將索引數據控制在 100G 以內,這樣的話,數據幾乎全部走記憶體來搜索,性能非常之高,一般可以在 1 秒以內,但是生成環境的數據量往往還是會很多,有大致四種方案:

1)數據預熱

平時看的人很多的數據,每隔一會兒,去搜索一下熱數據,刷到 filesystem cache 里去,後面用戶實際上來看這個熱數據的時候,就是直接從記憶體里搜索了;

2)冷熱分離

將大量的訪問很少、頻率很低的數據,單獨寫一個索引,然後將訪問很頻繁的熱數據單獨寫一個索引。最好是將冷數據寫入一個索引中,然後熱數據寫入另外一個索引中,這樣可以確保熱數據在被預熱之後,盡量都讓他們留在 filesystem os cache 里,別讓冷數據給沖刷掉。

3)es+Hbase架構: 

es只存儲索引欄位,其他數據放到mysql/hbase中;

舉例說明:id,name,age …. 30 個欄位。現在搜索,只需要根據 id,name,age 三個欄位來搜索。如果往 es 里寫入一行數據所有的欄位,就會導致說 90% 的數據是不用來搜索的,結果硬是佔據了 es 機器上的 filesystem cache 的空間,單條數據的數據量越大,就會導致 filesystem cahce 能快取的數據就越少。其實,僅僅寫入 es 中要用來檢索的少數幾個欄位就可以了,比如說就寫入es id,name,age 三個欄位,然後你可以把其他的欄位數據存在 mysql/hbase 里,我們一般是建議用 es + hbase 這麼一個架構。

4)document 模型設計
對於 MySQL,我們經常有一些複雜的關聯查詢。es 裡面的複雜的關聯查詢盡量別用,一旦用了性能一般都不太好。

要先在 Java 系統里就完成關聯,將關聯好的數據直接寫入 es 中。搜索的時候,就不需要利用 es 的搜索語法來完成 join 之類的關聯搜索了。

2、分頁查詢痛點及解決方案:

假設現在要查詢第100頁的10條數據,但是對於es來說,from=1000000,size=100,這時 es需要從各個分片上查詢出來10000100條數據,然後匯總計算後從其中取出100條。如果有5個分片則需要查詢出來5*10000100條數據,如果現在有並發的100個查詢請求,就會有50億左右的數據,佔用的記憶體是非常高的,所以在使用es的分頁查詢過程中,剛開始翻頁可能速度比較快,可能到第一百頁查詢就需要4-5s,翻到1000頁以後,系統資源佔用成指數級上升,很容易就會出現OOM直接報錯。

分頁方案:

1)基本的from-size查詢,es為了避免深度分頁帶來的記憶體開銷,from最大值設定到了10000,目前後台運營的翻頁最多關心近10頁的數據;

2)search after按照第一個檢索到的最後顯示的「balance」和『_id』值,作為下一個檢索search_after的參數,例如假定size是10,當查詢990-1000時,通過上次傳遞的最後一個檢索到的值,在分片上就可以取到10條文檔,不支援上一頁查詢。

3)scroll查詢

scroll查詢原理是在第一次查詢的時候一次性生成一個快照,根據上一次的查詢的id來進行下一次的查詢,這個就類似於關係型資料庫的游標,然後每次滑動都是根據產生的游標id進行下一次查詢,這種性能比上面說的分頁性能要高出很多,基本都是毫秒級的。

注意點:scroll不支援跳頁查詢。

使用場景:對實時性要求不高的查詢。

程式碼:

設置查詢條件

BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
       QueryBuilder builder = QueryBuilders.queryStringQuery("123456").field("code");
       boolQueryBuilder.must(QueryBuilders.termQuery("logType""10"))
               .must(builder);

首次查詢

  • 第一次查詢,跟平時的search查詢一樣需要設置index和type以及查詢條件。
  • 如果把查詢類型設置成SCAN,那麼不能獲取結果並且不支援排序,只能獲得scrollId,如果使用默認設置或者不設置,那麼第一次在獲取id的同時也可以獲取到查詢結果。
  • 這個size大小的意思不是總分頁的大小,實際數量應該是:所以實際返回的數量是:分片的數量*size
  • 滾動時間設置是指在這個查詢搜索結果的快取時間,時間不能太久,畢竟記憶體空間是有限的。
SearchResponse response1 = client.prepareSearch("_audit_0221").setTypes("_log_0221")
                    .setQuery(boolQueryBuilder)
                    .setSearchType(.setSearchType(SearchType.DEFAULT))
                    .setSize(10).setScroll(TimeValue.timeValueMinutes(5))
                    .addSort("logTime"SortOrder.DESC)
                    .execute().actionGet();//第一次查詢
for (SearchHit searchHit : response1.getHits().hits()) {
            biz handle....;
}

第二次查詢

            for (SearchHit searchHit : response1.getHits().hits()) {
            }
                    .execute().actionGet();
        }

4) 利用scroll-scan遍曆數據

使用場景:500w用戶,需要遍歷所有用戶發送數據,並且對順序沒有要求,這個時候我們可以使用scroll-scan。

查詢
 SearchResponse response = client.prepareSearch("_audit_0221").setTypes("_log_0221")
                    .setQuery(boolQueryBuilder)
                    .setSearchType(SearchType.SCAN)
                    .setSize(5).setScroll(TimeValue.timeValueMinutes(5))
                    .addSort("logTime"SortOrder.DESC)
                    .execute().actionGet();
獲取結果
                .execute().actionGet();
 
            for (SearchHit searchHit : response1.getHits().hits()) {
            }
                    .execute().actionGet();
}

scroll和scroll-scan區別

  1. scroll支援排序,scroll-scan不支援排序,是按照索引順序返回,可以提高查詢效率。

  2. scroll-scan第一次查詢只支援返回id,沒有結果。

總結:

    1. es的分頁查詢不支援深度分頁,如果偏要使用要結合具體業務場景進行使用。不能當成關係型資料庫中的分頁進行使用。
    2. 要想提高產品體驗和查詢效率不能過於依賴技術,要結合需求進行分析以提高體驗,因為很多搜索類產品都不支援深度分頁。
    3. 如果在不涉及排序的情況下盡量使用scroll-scan,它是按照索引順序返回,提高效率。