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

房上的貓發表於2021-08-25

系列目錄

記憶體吞金獸(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,它是按照索引順序返回,提高效率。

相關文章