ES 萬條以外分頁檢索功能實現及注意事項

背景

以 ES 存儲日志,且需要對日志進行分頁檢索,當數據量過大時,就面臨 ES 萬條以外的數據檢索問題,如何利用滾動檢索實現這個需求呢?本文介紹 ES 分頁檢索萬條以外的數據實現方法及注意事項。

需求分析

在這里插入圖片描述
用 ES 存儲數據,分頁檢索,當 ES 數據量過大時,在頁面上直接點擊最后一頁時,怎么保證請求能正常返回?

常規思路就是,超過萬條以后,使用滾動檢索,但需要注意:編寫滾動檢索的分頁查詢時,滾動請求的 size 一定不能用頁面分頁參數的 pageSize ,要能快速滾動到目標頁所在的數據,最好以 ES 最大檢索窗口值。

算法要點

第一,滾動檢索的 Request 請求不能包含 from 屬性, 且設置了 size 參數后,以后的每次滾動返回的數據量都以 size 為主。

第二,滾動獲取數據的 size 選取。 滾動分頁檢索高效的關鍵是不能以頁面分頁參數 pageSize 作為滾動請求的 size ,而是以一個較大的數,或者直接以 ES 默認的滾動窗口最大值 10000 作為每批次獲取的數據量。

第三,計算目標頁的數據所在的位置。

  1. 根據分頁參數計算出目標數據的位置是 [(pageSize-1)*pageSize, pageSize * pageNo] ,為了拿到目標頁的數據,總共的數據量 total = pageNo * pageSize
  2. 目標數據在最終數據中的真正范圍決定因素:mode = total % 10000
  3. 計算滾動請求幾次能拿到目標數據。實際需要滾動請求的次數 scrollCount = mode == 0 ? total/ esWindowCount : (total/ esWindowCount + 1)
  4. 目標頁的數據有沒有分布在兩次請求中。當 10000 % pageSize !=0 時,說明這一頁的數據會橫跨兩次 ES 請求。例如 pageSize =15,pageNo = 2667,total = 40005,目標頁的數據包含在最后兩次請求中,倒數第二次請求中有 10 條數據,最后一次請求中有 5 條數據,合起來才是一整頁的 15 條數據。
  5. 最后一頁數據不足 pageSize 時,最后一頁數據真正的長度。

第四,分頁數據所在范圍處理。 當最后一批次獲取到數據后,從中摘出目標頁的數據時,需要考慮的四種情況,主要是 mode 和最終獲取的數據總長度直接的關系:

在這里插入圖片描述
case 1:上圖左,mode=0 時存在最后一頁不足 size 的情況,realSize = size - (windowSize-length)

case 2:上圖右,length < mode 時,最后一頁不足 size 的情況,realSize = size - (mode -length)

最終的數據區間是 [from,to ] = [ length -realSize,length -1 ]
數據總長度 = end -start +1 = realSize
在這里插入圖片描述
case 3 :上圖左,分頁數據在 mode 往前推 size 條。
case 4:上圖右,分頁數據橫跨兩次請求,兩批數據組合成一頁數據。

編碼實現

編寫 ES 滾動分頁檢索請求,處理超過萬條之外的查詢操作:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.search.builder.SearchSourceBuilder;import java.io.IOException;
import java.util.*;@Slf4j
public class EsPageUtil {/*** 真正的 ES 連接對象*/private RestHighLevelClient client;public void initClient() {// TODO 初始化 client 對象}/*** 使用 DSL JSON 配置創建檢索請求 Builder* @param queryJson* @return*/public SearchSourceBuilder createSearchSource(String queryJson) {if (StringUtils.isEmpty(queryJson)) {log.error("ElasticSearch dsl config is empty.");return null;}SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();try {SearchModule searchModule = new SearchModule(Settings.EMPTY, false, Collections.emptyList());NamedXContentRegistry registry = new NamedXContentRegistry(searchModule.getNamedXContents());XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(registry, LoggingDeprecationHandler.INSTANCE, queryJson);searchSourceBuilder.parseXContent(parser);return searchSourceBuilder;} catch (Exception e) {log.error("Parse dsl error.", e);return null;}}/*** ES 分頁查詢:區分萬條以內還是萬條以外* @param pageSize  分頁size* @param pageNo    查詢頁數* @param indices   目標索引* @param queryJson 查詢 DSL JSON 格式字符串* @return*/public Map<String, Object> queryByPage(int pageSize, int pageNo, String[] indices, String queryJson) {SearchSourceBuilder searchSourceBuilder = createSearchSource(queryJson);if (searchSourceBuilder == null) {return null;}// 創建請求對象SearchRequest searchRequest = new SearchRequest(indices).source(searchSourceBuilder);Map<String, Object> result = new HashMap<>();List<Map<String, Object>> data = null;int total = pageSize * pageNo ;int maxEsWindow = 10000;try {if (total <= 10000) {// 萬條以內,直接查詢:設置 from , size 屬性searchSourceBuilder .from((pageNo - 1) * pageSize) .size(pageSize);SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);data =  parseResponseToListData(response);} else {// 萬條以外,以 ES 最大窗口值查詢:只設置size 屬性searchSourceBuilder.size(maxEsWindow);data = scrollQuery(maxEsWindow, pageSize, total, searchRequest);}} catch (IOException e) {log.error("ElasticSearch query error.", e);}result.put("total" , 0);result.put("data" , data);return result;}/*** 滾動查詢** @param esWindowCount* @param pageSize* @param total* @param searchRequest* @return*/private List scrollQuery(int esWindowCount, int pageSize, int total , SearchRequest searchRequest) {List pageData = new ArrayList(pageSize);//創建滾動,指定滾動查詢保持的時間final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(10L));//添加滾動searchRequest.scroll(scroll);//提交第一次請求SearchResponse searchResponse = null;String scrollId = null;try {searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);//獲取滾動查詢idscrollId = searchResponse.getScrollId();} catch (IOException e) {log.error("Elasticsearch request error.", e);return pageData;}int counter = 2;int mode = total % esWindowCount;int realPageCount = mode == 0 ? total/ esWindowCount : (total/ esWindowCount + 1);while (counter <= realPageCount) {// 設置滾動查詢id,從id開始繼續向下查詢SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);// 重置查詢時間,若不進行重置,則在提交的第一次請求中設置的時間結束,滾動查詢將失效scrollRequest.scroll(scroll);// 提交請求,獲取結果try {searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);} catch (IOException e) {log.error("Elasticsearch scroll request error.", e);}// size 非 10 的整數,則當前頁數據橫跨兩個 Scroll 請求if (mode != 0 && mode < pageSize && counter == (realPageCount -1)) {collectFirstPart(searchResponse, pageData, mode, pageSize);}// 更新滾動查詢idscrollId = searchResponse.getScrollId();counter++;}// 收集最后一次響應結果中的數據collectPageData(searchResponse, pageData, mode, pageSize, esWindowCount);//  滾動查詢結束時,清除滾動ClearScrollRequest clearScrollRequest = new ClearScrollRequest();clearScrollRequest.addScrollId(scrollId);try {client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);} catch (IOException e) {log.error("Elasticsearch clear scroll info error.", e);}return pageData;}/*** @param searchResponse* @param mode* @param size* @return*/public void collectFirstPart(SearchResponse searchResponse, List<Map<String, Object>> firstPartData, int mode, int size) {int firstPartCount = size - mode;// 只截取響應結果中的 結尾 size - mode 部分的內容SearchHits hits = searchResponse.getHits();SearchHit[] dataList = hits.getHits();int from = dataList.length - firstPartCount;for (int i = from; i < dataList.length; i++) {firstPartData.add(dataList[i].getSourceAsMap());}log.info("Mode less than size, first part data is here {} .", firstPartCount);}/*** 滾動到最后一組數據中包含目標頁的數據,從中摘出來* @param searchResponse* @param mode* @param size* @param esWindowCount* @return*/public void collectPageData(SearchResponse searchResponse, List<Map<String, Object>> pageData, int mode, int size, int esWindowCount) {SearchHits hits = searchResponse.getHits();SearchHit[] dataList = hits.getHits();int from = 0;int length = dataList.length;if (mode == 0) { // 剛好在萬條結尾// 不夠一頁if (length < esWindowCount) {int realSize = size - (esWindowCount - length);from = (length - realSize ) >= 0 ? (length - realSize ) : 0;} else {// 總長夠一頁from = length == esWindowCount ? (length - size) : 0;}} else if (length < mode){ // 最后一頁且總長不足 sizeint realSize = size - (mode - length);from = (length - realSize) >= 0 ? (length - realSize) : 0;} else if (mode > size){ // 中間部分from = (mode - size) >= 0 ? (mode -size) : 0;} else  { // mode < size ,說明是一頁數據的下半部分from = 0;size = mode;log.info("Page data is across two request ,this response has {} .", mode);}// 收集目標數據for (int i = from; i< from + size && i < length; i++) {pageData.add(dataList[i].getSourceAsMap());}}/*** 解析 ES 響應結果為數據集合* @param response* @return*/public static List<Map<String, Object>> parseResponseToListData(SearchResponse response){List<Map<String, Object>> listData = new ArrayList<>();if (response == null) {return listData;}// 遍歷響應結果SearchHits hits = response.getHits();SearchHit[] hitArray = hits.getHits();listData = new ArrayList<>(hitArray.length);for (SearchHit hit : hitArray) {Map<String, Object> sourceAsMap = hit.getSourceAsMap();listData.add(sourceAsMap);}// 返回結果return listData;}
}

啟示錄

滾動查詢時優化了 size 用一萬,相比用頁面的分頁參數 pageSize ,可以解決數據量過大時,直接從頁面點擊最后一頁導致頁面卡死長時間無響應的問題。

頁面分頁參數最大不過 100,當總數量幾百萬、pageSize=10,分頁跳轉查詢后面某頁 如 3000 時,ES 的滾動請求次數 是 3000 次,而優化后滾動請求 3次,第三次中的一萬條數據的最后10條即本頁的數據。

話說回來,ES 數據量過大時,用分頁查詢靠后的數據時,也沒多大的價值了,列表寬泛條件查詢結果過大時,誰看得過來呢?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/166227.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/166227.shtml
英文地址,請注明出處:http://en.pswp.cn/news/166227.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Redis 反序列化失敗

文章目錄 問題原序列化配置修改配置解決方法 問題 com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of org.springframework.security.core.authority.SimpleGrantedAuthority (although at least one Creator exists): cannot deser…

css圖片縮放屬性object-fit說明

object-fit 屬性可以設置以下值&#xff1a; 屬性值說明例子fill填充容器&#xff0c;可能會改變圖片的比例。object-fit: fill;contain保持圖片的原始比例&#xff0c;確保圖片完全包含在容器內。object-fit: contain;cover保持圖片的原始比例&#xff0c;確保圖片覆蓋整個容…

性能優化中使用Profiler進行頁面卡頓的排查及解決方式

文章目錄 一、前言二、頁面卡頓的排查方式1、耗時操作的監控2、頁面卡頓的監控 三、參考鏈接 一、前言 程序的優化在做過線上bug處理&#xff0c;布局層級優化&#xff0c;項目依賴庫版本更新&#xff0c;重復庫合并&#xff0c;刪除未使用的資源&#xff0c;刪除冗余的庫&…

機器學習【01】相關環境的安裝

學習實例 參考資料&#xff1a;聯邦學習實戰{楊強}https://book.douban.com/subject/35436587/ 項目地址&#xff1a;https://github.com/FederatedAI/Practicing-Federated-Learning/tree/main/chapter03_Python_image_classification 一、環境準備 GPU安裝CUDA、cuDNN pytho…

ComboGrid中快捷鍵Enter使用

為了實現當前元素&#xff0c;回車時有值跳轉到下一個元素&#xff0c;無值則查詢。 定義元素時使用快捷鍵 $.fn.combogrid.defaults.keyHandler.up.call(this);調用combogrid默認的快捷鍵 $(#cs).combogrid({width: 360,placeholder: 測試...,panelWidth: 1000,qParams: {pJ…

letcode::數組中的第k個最大元素

數組中的第k個最大元素 給定整數數組 nums 和整數 k&#xff0c;請返回數組中第 k 個最大的元素。 請注意&#xff0c;你需要找的是數組排序后的第 k 個最大的元素&#xff0c;而不是第 k 個不同的元素。 你必須設計并實現時間復雜度為 O(n) 的算法解決此問題。 示例 1: 輸入: …

PHP 語法||PHP 變量

PHP 腳本在服務器上執行&#xff0c;然后將純 HTML 結果發送回瀏覽器。 基本的 PHP 語法 PHP 腳本可以放在文檔中的任何位置。 PHP 腳本以 <?php 開始&#xff0c;以 ?> 結束&#xff1a; <?php // PHP 代碼 ?> 值得一提的是&#xff0c;通過設定php.ini的相…

nvm-切換node版本工具安裝-方便好用

去官網下載&#xff1a; https://github.com/coreybutler/nvm-windows#installation--upgrades 網站進去后點擊下載&#xff0c;點擊那個exe文件就下載本地&#xff0c;然后雙擊安裝 安裝nvm 就直接按照窗口提示的下一步就行&#xff0c;如果改了某些地方會不成功&#xf…

數字孿生技術:提升UI交互性與個性化設計

隨著數字化時代的到來&#xff0c;數字孿生技術正在逐漸改變我們的生活和工作方式。數字孿生是一種復制現實世界系統或實體的技術&#xff0c;通過創建數字模型來模擬現實世界中的各種行為和事件。這種技術不僅為人們提供了一個全新的視角來看待和解決問題&#xff0c;同時也為…

內衣專用洗衣機怎么樣?口碑最好的小型洗衣機

隨著人們的生活水平的提升&#xff0c;越來越多小伙伴來開始追求更高的生活水平&#xff0c;一些智能化的小家電就被發明出來&#xff0c;而且內衣洗衣機是其中一個。現在通過內衣褲感染到細菌真的是越來越多&#xff0c;所以我們對內衣褲的清洗頻次會高于普通衣服&#xff0c;…

Spring Boot 3.2發布:大量Java 21的支持上線,改進可觀測性

就在今天凌晨&#xff0c;Spring Boot 3.2正式發布了&#xff01;該版本是在Java 21正式發布之后的重要支持版本&#xff0c;所以在該版本中包含大量對Java 21支持的優化。 下面&#xff0c;我們分別通過Spring官方發布的博文和Josh Long長達80分鐘的介紹視頻&#xff0c;一起…

飛翔的鳥游戲

一.準備工作 首先創建一個新的Java項目命名為“飛翔的鳥”&#xff0c;并在src中創建一個包命名為“com.qiku.bird"&#xff0c;在這個包內分別創建4個類命名為“Bird”、“BirdGame”、“Column”、“Ground”&#xff0c;并向需要的圖片素材導入到包內。 二.代碼呈現 pa…

【醫學圖像處理】超詳細!PET圖像批量預處理

目錄 一、單個PET圖像預處理1、使用[MRIConvert](https://pan.baidu.com/s/1cn3kgeVRir8HvP6HHm0M0Q?pwd5rt5)處理DCM2、MRI和PET數據預處理過程1&#xff09; 打開matlab命令行輸入spm pet&#xff0c;打開SMP12&#xff0c;界面如下2&#xff09; Realign&#xff0c;只需要…

【Vue】插值表達式

作用&#xff1a;利用表達式進行插值渲染 語法&#xff1a;{ { 表達式 } } 目錄 案例一&#xff1a; 案例二&#xff1a; 案例三&#xff1a; ?編輯 注意&#xff1a; 案例一&#xff1a; <!DOCTYPE html> <html lang"en"> <head><me…

項目中如何配置數據可視化展現

在現今數據驅動的時代&#xff0c;可視化已逐漸成為數據分析的主要途徑&#xff0c;可視化大屏的廣泛使用便應運而生。很多公司及政務機構&#xff0c;常利用大屏的手段展現其實力或演示業務&#xff0c;可視化的效果能讓觀者更快速的理解結果并直觀的看到數據展現。因此&#…

加速軟件開發:自動化測試在持續集成中的重要作用!

持續集成的自動化測試 如今互聯網軟件的開發、測試和發布&#xff0c;已經形成了一套非常標準的流程&#xff0c;最重要的組成部分就是持續集成&#xff08;Continuous integration&#xff0c;簡稱CI&#xff0c;目前主要的持續集成系統是Jenkins&#xff09;。 那么什么是持…

教育+AIGC開局之年:教育派作業幫、科技派科大訊飛同路不同道

配圖來自Canva可畫 與往年相比&#xff0c;今年的雙11顯得格外冷清&#xff0c;GMV&#xff08;商品交易總額&#xff09;數據和增長數據無人提及&#xff0c;京東、淘寶天貓、抖音、快手等平臺的火藥味都淡了。一片祥和有序的雙11氛圍中&#xff0c;昔日的K12教育企業與科技企…

sqlserver寫入中文亂碼問題

sqlserver寫入中文亂碼問題解決方案 首先查看sqlserver數據庫編碼 首先查看sqlserver數據庫編碼 查詢語句&#xff1a;SELECT COLLATIONPROPERTY(Chinese_PRC_Stroke_CI_AI_KS_WS, CodePage)&#xff1b; 對應的編碼&#xff1a; 936 簡體中文GBK 950 繁體中文BIG5 437 美國/加…

算法的10大排序

10大排序算法--python 一顆星--選擇排序一顆星--冒泡排序一顆星--插入排序兩顆星--歸并排序&#xff08;遞歸-難&#xff09;三顆星--桶排序三顆星--計數排序四顆星--基數排序四顆星--快速排序&#xff0c;尋找標志位&#xff08;遞歸-難&#xff09;四顆星--又是比較難的希爾排…