<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    千萬級數據查詢中CK、ES、RediSearch方案的優化

    VSole2022-06-20 17:31:17

    前言

    在開發中遇到一個業務訴求,需要在千萬量級的底池數據中篩選出不超過 10W 的數據,并根據配置的權重規則進行排序、打散(如同一個類目下的商品數據不能連續出現 3 次)。下面對該業務訴求的實現,設計思路和方案優化進行介紹。

    對“千萬量級數據中查詢 10W 量級的數據”設計了如下方案:

    • 多線程+CK 翻頁方案
    • ES scroll scan 深翻頁方案
    • ES+Hbase 組合方案
    • RediSearch+RedisJSON 組合方案

    初版設計方案

    整體方案設計為:

    • 先根據配置的「篩選規則」,從底池表中篩選出「目標數據」
    • 在根據配置的「排序規則」,對「目標數據」進行排序,得到「結果數據」

    技術方案如下:

    每天運行導數任務,把現有的千萬量級的底池數據(Hive 表)導入到 Clickhouse 中,后續使用 CK 表進行數據篩選。

    將業務配置的篩選規則和排序規則,構建為一個「篩選 + 排序」對象 SelectionQueryCondition。

    從 CK 底池表取「目標數據」時,開啟多線程,進行分頁篩選,將獲取到的「目標數據」存放到 result 列表中。

    //分頁大小  默認 5000
    int pageSize = this.getPageSize();
    //頁碼數
    int pageCnt = totalNum / this.getPageSize() + 1;
    
    List<Map<String, Object>> result = Lists.newArrayList();
    ListList<Map<String, Object>>>> futureList = new ArrayList<>(pageCnt);
    
    //開啟多線程調用
    for (int i = 1; i <= pageCnt; i++) {
        //將業務配置的篩選規則和排序規則 構建為 SelectionQueryCondition 對象
        SelectionQueryCondition selectionQueryCondition = buildSelectionQueryCondition(selectionQueryRuleData);
        selectionQueryCondition.setPageSize(pageSize);
        selectionQueryCondition.setPage(i);
        futureList.add(selectionQueryEventPool.submit(new QuerySelectionDataThread(selectionQueryCondition)));
    }
    
    
    for (Future<List<Map<String, Object>>> future : futureList) {
        //RPC 調用
        List<Map<String, Object>> queryRes = future.get(20, TimeUnit.SECONDS);
        if (CollectionUtils.isNotEmpty(queryRes)) {
            // 將目標數據存放在 result 中
            result.addAll(queryRes);
        }
    }
    

    ④對目標數據 result 進行排序,得到最終的「結果數據」。

    CK 分頁查詢

    在「初版設計方案」章節的第 3 步提到了「從 CK 底池表取目標數據時,開啟多線程,進行分頁篩選」。此處對 CK 分頁查詢進行介紹。

    ①封裝了 queryPoolSkuList 方法,負責從 CK 表中獲得目標數據。該方法內部調用了 sqlSession.selectList 方法。

    public List<Map<String, Object>> queryPoolSkuList( Map<String, Object> params ) {
        List<Map<String, Object>> resultMaps = new ArrayList<>();
    
        QueryCondition queryCondition = parseQueryCondition(params);
        List<Map<String, Object>> mapList = lianNuDao.queryPoolSkuList(getCkDt(),queryCondition);
        if (CollectionUtils.isNotEmpty(mapList)) {
            for (Map<String,Object> data : mapList) {
                resultMaps.add(camelKey(data));
            }
        }
        return resultMaps;
    }
    


    // lianNuDao.queryPoolSkuList
    
    @Autowired
    @Qualifier("ckSqlNewSession")
    private SqlSession sqlSession;
    
    public List<Map<String, Object>> queryPoolSkuList( String dt, QueryCondition queryCondition ) {
        queryCondition.setDt(dt);
        queryCondition.checkMultiQueryItems();
        return sqlSession.selectList("LianNu.queryPoolSkuList",queryCondition);
    }
    

    ②sqlSession.selectList 方法中調用了和 CK 交互的 queryPoolSkuList 查詢方法,部分代碼如下:

    <select id="queryPoolSkuList" parameterType="com.jd.bigai.domain.liannu.QueryCondition" resultType="java.util.Map">
        select sku_pool_id,i
        tem_sku_id,
        skuPoolName,
        price,
        ...
        ...
        businessType
        from liannu_sku_pool_indicator_all
        where
        dt=#{dt}
        and
        <foreach collection="queryItems" separator=" and " item="queryItem" open=" " close=" " >
            
                "queryItem.type == 'equal'">
                    ${queryItem.field} = #{queryItem.value}
                
                ...
                ...
            
        foreach>
        <if test="orderBy == null">
            group by sku_pool_id,item_sku_id
        if>
        <if test="orderBy != null">
            group by sku_pool_id,item_sku_id,${orderBy} order by ${orderBy} ${orderAd}
        if>
        <if test="limitEnd != 0">
            limit #{limitStart},#{limitEnd}
        if>
    select>
    

    ③可以看到,在 CK 分頁查詢時,是通過 limit #{limitStart},#{limitEnd} 實現的分頁。

    limit 分頁方案,在「深翻頁」時會存在性能問題。初版方案上線后,在 1000W 量級的底池數據中篩選 10W 的數據,最壞耗時會達到 10s~18s 左右。

    使用 ES Scroll Scan 優化深翻頁

    對于 CK 深翻頁時候的性能問題,進行了優化,使用 Elasticsearch 的 scroll scan 翻頁方案進行優化。

    | ES 的翻頁方案

    ES 翻頁,有下面幾種方案:

    • from + size 翻頁
    • scroll 翻頁
    • scroll scan 翻頁
    • search after 翻頁

    對上述幾種翻頁方案,查詢不同數目的數據,耗時數據如下表:

    | 耗時數據

    此處,分別使用 Elasticsearch 的 scroll scan 翻頁方案、初版中的 CK 翻頁方案進行數據查詢,對比其耗時數據。

    如上測試數據,可以發現,以十萬,百萬,千萬量級的底池為例:

    • 底池量級越大,查詢相同的數據量,耗時越大
    • 查詢結果 3W 以下時,ES 性能優;查詢結果 5W 以上時,CK 多線程性能優

    ES+Hbase 組合查詢方案

    在「使用 ES Scroll Scan 優化深翻頁」中,使用 Elasticsearch 的 scroll scan 翻頁方案對深翻頁問題進行了優化,但在實現時為單線程調用,所以最終測試耗時數據并不是特別理想,和 CK 翻頁方案性能差不多。

    在調研階段發現,從底池中取出 10W 的目標數據時,一個商品包含多個字段的信息(CK 表中一行記錄有 150 個字段信息),如價格、會員價、學生價、庫存、好評率等。

    對于一行記錄,當減少獲取字段的個數時,查詢耗時會有明顯下降。如對 sku1的商品,從之前獲取價格、會員價、學生價、親友價、庫存等 100 個字段信息,縮減到只獲取價格、庫存這兩個字段信息。

    如下圖所示,使用 ES 查詢方案,對查詢同樣條數的場景(從千萬級底池中篩選出 7W+ 條數據),獲取的每條記錄的字段個數從 32 縮減到 17,再縮減到 1個(其實是兩個字段,一個是商品唯一標識 sku_id,另一個是 ES 對每條文檔記錄的 doc_id)時,查詢的耗時會從 9.3s 下降到 4.2s,再下降到 2.4s。

    從中可以得出如下結論:

    • 一次 ES 查詢中,若查詢字段和信息較多,fetch 階段的耗時,遠大于 query 階段的耗時。
    • 一次 ES 查詢中,若查詢字段和信息較多,通過減少不必要的查詢字段,可以顯著縮短查詢耗時。

    下面對結論中涉及的 query 和 fetch 查詢階段進行補充說明。

    | ES 查詢的兩個階段

    在 ES 中,搜索一般包括兩個階段:

    • query 階段:根據查詢條件,確定要取哪些文檔(doc),篩選出文檔 ID(doc_id)
    • fetch 階段:根據 query 階段返回的文檔 ID(doc_id),取出具體的文檔(doc)

    | 組合使用 Hbase

    沿著這個優化思路,設計了一種新的查詢方案:

    • ES 僅用于條件篩選,ES 的查詢結果僅包含記錄的唯一標識 sku_id(其實還包含 ES 為每條文檔記錄的 doc_id)
    • Hbase 是列存儲數據庫,每列數據有一個 rowKey。利用 rowKey 篩選一條記錄時,復雜度為 O(1)。(類似于從 HashMap 中根據 key 取 value)
    • 根據 ES 查詢返回的唯一標識 sku_id,作為 Hbase 查詢中的 rowKey,在 O(1) 復雜度下獲取其他信息字段,如價格,庫存等

    使用 ES + Hbase 組合查詢方案,在線上進行了小規模的灰度測試。在 1000W 量級的底池數據中篩選 10W 的數據,對比 CK 翻頁方案,最壞耗時從 10~18s 優化到了 3~6s 左右。

    也應該看到,使用 ES + Hbase 組合查詢方案,會增加系統復雜度,同時數據也需要同時存儲到 ES 和 Hbase。

    RediSearch+RedisJSON 優化方案

    RediSearch 是基于 Redis 構建的分布式全文搜索和聚合引擎,能以極快的速度在 Redis 數據集上執行復雜的搜索查詢。

    RedisJSON 是一個 Redis 模塊,在 Redis 中提供 JSON 支持。RedisJSON 可以和 RediSearch 無縫配合,實現索引和查詢 JSON 文檔。

    根據一些參考資料,RediSearch + RedisJSON 可以實現極高的性能,可謂碾壓其他 NoSQL 方案。在后續版本迭代中,可考慮使用該方案來進一步優化。

    下面給出 RediSearch + RedisJSON 的部分性能數據。

    | RediSearch 性能數據

    在同等服務器配置下索引了 560 萬個文檔 (5.3GB),RediSearch 構建索引的時間為 221 秒,而 Elasticsearch 為 349 秒。RediSearch 比 ES 快了 58%。

    數據建立索引后,使用 32 個客戶端對兩個單詞進行檢索,RediSearch 的吞吐量達到 12.5K ops/sec,ES 的吞吐量為 3.1K ops/sec,RediSearch 比 ES 要快 4 倍。

    同時,RediSearch 的延遲為 8ms,而 ES 為 10ms,RediSearch 延遲稍微低些。

    | RedisJSON 性能數據

    根據官網的性能測試報告,RedisJson + RedisSearch 可謂碾壓其他 NoSQL:

    • 對于隔離寫入(isolated writes),RedisJSON 比 MongoDB 快 5.4 倍,比 ES 快 200 倍以上
    • 對于隔離讀取(isolated reads),RedisJSON 比 MongoDB 快 12.7 倍,比 ES 快 500 倍以上

    在混合工作負載場景中,實時更新不會影響 RedisJSON 的搜索和讀取性能,而 ES 會受到影響:

    • RedisJSON 支持的操作數/秒比 MongoDB 高約 50 倍,比 ES 高 7 倍/秒
    • RedisJSON 的延遲比 MongoDB 低約 90 倍,比 ES 低 23.7 倍

    此外,RedisJSON 的讀取、寫入和負載搜索延遲,在更高的百分位數中遠比 ES 和 MongoDB 穩定。

    當增加寫入比率時,RedisJSON 還能處理越來越高的整體吞吐量。而當寫入比率增加時,ES 會降低它可以處理的整體吞吐量。

    總結

    本文從一個業務訴求觸發,對“千萬量級數據中查詢 10W 量級的數據”介紹了不同的設計方案。

    對于在 1000W 量級的底池數據中篩選 10W 的數據的場景,不同方案的耗時如下:

    • 多線程+CK 翻頁方案,最壞耗時為 10s~18s
    • 單線程+ES scroll scan 深翻頁方案,相比 CK 方案,并未見到明顯優化
    • ES+Hbase 組合方案,最壞耗時優化到了 3s~6s
    • RediSearch+RedisJSON 組合方案,后續會實測該方案的耗時
    轉自:變速風聲
    鏈接:https://juejin.cn/post/7104090532015505416
    stringredisearch
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    每天運行導數任務,把現有的千萬量級的底池數據(Hive 表)導入到 Clickhouse 中,后續使用 CK 表進行數據篩選。
    使用java和redis實現一個簡單的熱搜功能,具備以下功能: 搜索欄展示當前登陸的個人用戶的搜索歷史記錄,刪除個人歷史記錄 用戶在搜索欄輸入某字符,則將該字符記錄下來 以zset格式存儲的redis中,記錄該字符被搜索的個數以及當前的時間戳 (用了DFA算法,感興趣的自己百度學習吧) 每當用戶查詢了已在redis存在了的字符時,則直接累加個數, 用來獲取平臺上最熱查詢的十條數據。(可以自己寫接
    通用日志文件系統是一種通用的日志記錄子系統,可供運行在內核模式和用戶模式下的應用程序使用,用于構建高性能的事務日志,并在驅動程序CLFS.sys中進行實現。通用日志文件系統在基本日志文件中生成事務日志。三種類型的記錄(控制記錄、基本記錄和截斷記錄((Control Record, Base Record, and Truncate Record))可以駐留在這些塊中。Base Record包含符號表,這些符號表存儲有關與基本日志文件關聯的客戶端上下文、容器上下文和安全上下文的信息。
    CobaltStrike ShellCode詳解
    2022-08-04 16:51:50
    接下來就是重點了,加載起來的這段shellcode開頭先將DF標志位置0,這里為什么這樣做后面會提到。
    一款功能強大的現代OSINT信息收集工具
    APISIX 安全評估
    2022-06-15 15:11:42
    背景 有大佬已經對apisix攻擊面[1]做過總結。 本文記錄一下自己之前的評估過程。 分析過程 評估哪些模塊? 首先我需要知道要評估啥,就像搞滲透時,我得先知道攻擊面在哪里。
    概述最近log4j爆出重大安全漏洞CVE-2021-44228。在觀測了一系列利用log4shell攻擊的活動后,安全研究人員捕獲了一批新樣本,其中包括StealthLoader。獲取了setup.exe,并且下載到兩個地方,然后創建進程。
    引言C3P0反序列化利用鏈是Java反序列化漏洞中比較經典的一條RCE利用鏈。最近看到有大佬對C3P0利用鏈不出網做了一些研究,在此基礎上,自己也系統地梳理一下各種姿勢的C3P0利用鏈,包括:Java原生態反序列化利用鏈-遠程加載惡意類Java原生態反序列化利用鏈改進-無需出網Json反序列化利用鏈-遠程加載惡意類Json反序列化利用鏈-無需出網這里將4個利用鏈的原理分析與具體實現分享給大家。
    Spring 視圖操縱漏洞
    2020-11-03 14:06:15
    聲明 由于傳播、利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,雷神眾測以及文章作者不為此承擔任何責任。雷神眾測擁有對此文章的修改和解釋權。如欲轉載或傳播此文章,必須保證此文章的完整性,包括版權聲明等全部內容。未經雷神眾測允許,不得任意修改或者增減此文章內容,不得以任何方式將其用于商業目的。因此我們的 ViewName *不滿足,自然是在 *resolveViewName 處理之后返回了*ThymeleafView *。
    近期,Unit 42的研究人員在分析Medusa(美杜莎)勒索軟件活動時,發現該活動的升級和勒索策略發生了很大變化。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类