cassandra高級操作之分頁的java實現(有項目具體需求)

来源:http://www.cnblogs.com/youzhibing/archive/2017/03/31/6653129.html
-Advertisement-
Play Games

接著上篇博客,我們來談談java操作cassandra分頁,需要註意的是這個分頁與我們平時所做的頁面分頁是不同的,具體有啥不同,大家耐著性子往下看。 上篇博客講到了cassandra的分頁,相信大家會有所註意:下一次的查詢依賴上一次的查詢(上一次查詢的最後一條記錄的全部主鍵),不像mysql那樣靈活 ...


  接著上篇博客,我們來談談java操作cassandra分頁,需要註意的是這個分頁與我們平時所做的頁面分頁是不同的,具體有啥不同,大家耐著性子往下看。

  上篇博客講到了cassandra的分頁,相信大家會有所註意:下一次的查詢依賴上一次的查詢(上一次查詢的最後一條記錄的全部主鍵),不像mysql那樣靈活,所以只能實現上一頁、下一頁這樣的功能,不能實現第多少頁那樣的功能(硬要實現的話性能就太低了)。

  我們先看看驅動官方給的分頁做法

  如果一個查詢得到的記錄數太大,一次性返回回來,那麼效率非常低,並且很有可能造成記憶體溢出,使得整個應用都奔潰。所以了,驅動對結果集進行了分頁,並返回適當的某一頁的數據。

一、設置抓取大小(Setting the fetch size)

  抓取大小指的是一次從cassandra獲取到的記錄數,換句話說,就是每一頁的記錄數;我們能夠在創建cluster實例的時候給它的fetch size指定一個預設值,如果沒有指定,那麼預設是5000

// At initialization:
Cluster cluster = Cluster.builder()
    .addContactPoint("127.0.0.1")
    .withQueryOptions(new QueryOptions().setFetchSize(2000))
    .build();

// Or at runtime:
cluster.getConfiguration().getQueryOptions().setFetchSize(2000);

  另外,statement上也能設置fetch size

Statement statement = new SimpleStatement("your query");
statement.setFetchSize(2000);

  如果statement上設置了fetch size,那麼statement的fetch size將起作用,否則則是cluster上的fetch size起作用。

  註意:設置了fetch size並不意味著cassandra總是返回準確的結果集(等於fetch size),它可能返回比fetch size稍微多一點或者少一點的結果集。

二、結果集迭代

  fetch size限制了每一頁返回的結果集的數量,如果你迭代某一頁,驅動會在後臺自動的抓取下一頁的記錄。如下例,fetch size = 20:

 

  預設情況下,後臺自動抓取發生在最後一刻,也就是當某一頁的記錄被迭代完的時候。如果需要更好的控制,ResultSet介面提供了以下方法:

    getAvailableWithoutFetching() and isFullyFetched() to check the current state;

    fetchMoreResults() to force a page fetch;

  以下是如何使用這些方法提前預取下一頁,以避免在某一頁迭代完後才抓取下一頁造成的性能下降:

ResultSet rs = session.execute("your query");
for (Row row : rs) {
    if (rs.getAvailableWithoutFetching() == 100 && !rs.isFullyFetched())
        rs.fetchMoreResults(); // this is asynchronous
    // Process the row ...
    System.out.println(row);
}

三、保存並重新使用分頁狀態

  有時候,將分頁狀態保存起來,對以後的恢復是非常有用的,想象一下:有一個無狀態Web服務,顯示結果列表,並顯示下一頁的鏈接,當用戶點擊這個鏈接的時候,我們需要執行與之前完全相同的查詢,除了迭代應該從上一頁停止的位置開始;相當於記住了上一頁迭代到了哪了,那麼下一頁從這裡開始即可。

  為此,驅動程式會暴露一個PagingState對象,該對象表示下一頁被提取時我們在結果集中的位置。

ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
// PagingState對象可以被序列化成字元串或位元組數組 String string
= pagingState.toString(); byte[] bytes = pagingState.toBytes();

  PagingState對象被序列化後的內容可以持久化存儲起來,也可用作分頁請求的參數,以備後續再次被利用,反序列化成對象即可:

PagingState.fromBytes(byte[] bytes);
PagingState.fromString(String str);

  請註意,分頁狀態只能使用完全相同的語句重覆使用(相同的查詢,相同的參數)。而且,它是一個不透明的值,只是用來存儲一個可以被重新使用的狀態值,如果嘗試修改其內容或將其使用在不同的語句上,驅動程式會拋出錯誤。

  具體我們來看下代碼,下例是模擬頁面分頁的請求,實現遍歷teacher表中的全部記錄:

  介面:

import java.util.Map;

import com.datastax.driver.core.PagingState;

public interface ICassandraPage
{
    Map<String, Object> page(PagingState pagingState);

}
View Code

  主體代碼:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.datastax.driver.core.PagingState;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.Statement;
import com.huawei.cassandra.dao.ICassandraPage;
import com.huawei.cassandra.factory.SessionRepository;
import com.huawei.cassandra.model.Teacher;

public class CassandraPageDao implements ICassandraPage
{
    private static final Session session = SessionRepository.getSession();
    
    private static final String CQL_TEACHER_PAGE = "select * from mycas.teacher;";
    
    @Override
    public Map<String, Object> page(PagingState pagingState)
    {
        final int RESULTS_PER_PAGE = 2;
        Map<String, Object> result = new HashMap<String, Object>(2);
        List<Teacher> teachers = new ArrayList<Teacher>(RESULTS_PER_PAGE);

        Statement st = new SimpleStatement(CQL_TEACHER_PAGE);
        st.setFetchSize(RESULTS_PER_PAGE);
        
        // 第一頁沒有分頁狀態
        if (pagingState != null)
        {            
            st.setPagingState(pagingState);
        }
        
        ResultSet rs = session.execute(st);
        result.put("pagingState", rs.getExecutionInfo().getPagingState());
        
        //請註意,我們不依賴RESULTS_PER_PAGE,因為fetch size並不意味著cassandra總是返回準確的結果集
        //它可能返回比fetch size稍微多一點或者少一點,另外,我們可能在結果集的結尾
        int remaining = rs.getAvailableWithoutFetching();
        for (Row row : rs)
        {
            Teacher teacher = this.obtainTeacherFromRow(row);
            teachers.add(teacher);
            
            if (--remaining == 0) 
            {
                break;
            }
        }
        result.put("teachers", teachers);
        return result;
    }

    private Teacher obtainTeacherFromRow(Row row)
    {
        Teacher teacher = new Teacher();
        teacher.setAddress(row.getString("address"));
        teacher.setAge(row.getInt("age"));
        teacher.setHeight(row.getInt("height"));
        teacher.setId(row.getInt("id"));
        teacher.setName(row.getString("name"));
        
        return teacher;
    }
 
}
View Code

  測試代碼:

import java.util.Map;

import com.datastax.driver.core.PagingState;
import com.huawei.cassandra.dao.ICassandraPage;
import com.huawei.cassandra.dao.impl.CassandraPageDao;

public class PagingTest
{
    
    public static void main(String[] args)
    {
        ICassandraPage cassPage = new CassandraPageDao();
        Map<String, Object> result = cassPage.page(null);
        PagingState pagingState = (PagingState) result.get("pagingState");
        System.out.println(result.get("teachers"));
        while (pagingState != null)
        {
            // PagingState對象可以被序列化成字元串或位元組數組
            System.out.println("==============================================");
            result = cassPage.page(pagingState);
            pagingState = (PagingState) result.get("pagingState");
            System.out.println(result.get("teachers"));
        }
    }
    
}
View Code

  我們來看看Statement的setPagingState(pagingState)方法:

 

四、偏移查詢

  保存分頁狀態,能夠保證從某一頁移動到下一頁很好地運行(也可以實現上一頁),但是它不滿足隨機跳躍,比如直接跳到第10頁,因為我們不知道第10頁的前一頁的分頁狀態。像這樣需要偏移查詢的特點,並不被cassandra原生支持,理由是偏移查詢效率低下(性能與跳過的行數呈線性反比),所以cassandra官方不鼓勵使用偏移量。如果非要實現偏移查詢,我們可以在客戶端模擬實現。但是性能還是呈線性反比,也就說偏移量越大,性能越低,如果性能在我們的接受範圍內,那還是可以實現的。例如,每一頁顯示10行,最多顯示20頁,這就意味著,當顯示第20頁的時候,最多需要額外的多抓取190行,但這也不會對性能造成太大的降低,所以數據量不大的話,模擬實現偏移查詢還是可以的。

  舉個例子,假設每頁顯示10條記錄,fetch size 是50,我們請求第12頁(也就是第110行到第119行):

  1、第一次執行查詢,結果集包含0到49行,我們不需要用到它,只需要分頁狀態;

  2、用第一次查詢得到的分頁狀態,執行第二次查詢;

  3、用第二次查詢得到的分頁狀態,執行第三次查詢。結果集包含100到149行;

  4、用第三次查詢得到的結果集,先過濾掉前10條記錄,然後讀取10條記錄,最後丟棄剩下的記錄,讀取的10條記錄則是第12頁需要顯示的記錄。

  我們需要嘗試著找到最佳的fetch size來達到最佳平衡:太小就意味著後臺更多的查詢;太大則意味著返回了更大的信息量以及更多不需要的行。

  另外,cassandra本身不支持偏移量查詢。在滿足性能的前提下,客戶端模擬偏移量的實現只是一種妥協。官方建議如下:

        1、使用預期的查詢模式來測試代碼,以確保假設是正確的

        2、設置最高頁碼的硬限制,以防止惡意用戶觸發跳過大量行的查詢

五、總結

  Cassandra對分頁的支持有限,上一頁、下一頁比較好實現。不支持偏移量的查詢,硬要實現的話,可以採用客戶端模擬的方式,但是這種場景最好不要用在cassandra上,因為cassandra一般而言是用來解決大數據問題,而偏移量查詢一旦數據量太大,性能就不敢恭維了。

  在我的項目中,索引修複用到了cassandra的分頁,場景如下:cassandra的表不建二級索引,用elasticsearch實現cassandra表的二級索引,那麼就會涉及到索引的一致性修複的問題,這裡就用到了cassandra的分頁,對cassandra的某張表進行全表遍歷,逐條與elasticsearch中的數據進行匹對,若elasticsearch中不存在,則在elasticsearch中新增,若存在而又不一致,則在elasticsearch中修複。具體elasticsearch怎麼樣實現cassandra的索引功能,在我後續博客中會專門的講解,這裡就不多說了。而在cassandra表進行全表遍歷的時候就需要用到分頁,因為表中數據量太大,億級別的數據不可能一次全部載入到記憶體中。

  工程附件


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題SQL scwksmlcls.wk_cls_c , scwklrgcls.wk_lrg_cls_nm , scwkmdlcls.wk_mdl_cls_nm , scwksmlcls.wk_sml_cls_nm , scwksmlcls.wk_cls_rmk FROM screqrsnsws IN ...
  • 建表 在這裡呢我們先來建立兩張有外鍵關聯的張表。 CREATE DATABASE db0206; USE db0206; CREATE TABLE `db0206`.`tbl_dept`( `id` INT(11) NOT NULL AUTO_INCREMENT, `deptName` VARCHA ...
  • 在統計查詢中,經常會用到count函數,這裡是基礎的 MYSQL 行轉列 以及基本的聚合函數count,與group by 以及distinct組合使用 ...
  • 閱讀目錄 (1)選擇最有效率的表名順序(只在基於規則的優化器中有效) (2)WHERE子句中的連接順序 (3)SELECT子句中避免使用 ‘ * ‘ (4)減少訪問資料庫的次數 (5)在SQL*Plus , SQL*Forms和Pro*C中重新設置ARRAYSIZE參數, 可以增加每次資料庫訪問的檢 ...
  • 感慨一句,現在寫的sql語句自己是越來越看不懂了 。。。囧 使用場景:最近做畢設的時候碰見這麼一個場景(仿攜程網) 先看大的,按航班號查詢出3條數據。這個好弄 group by(航班)就行。點擊訂票,跳出兩個欄目,經濟艙和頭等艙分別帶餘票數。查航班餘票數好解決,條件那加 count(航班)就行。難點 ...
  • 本文出處:http://www.cnblogs.com/wy123/p/6646143.html SQLServer中開啟CDC之後,在某些情況下會導致事務日誌空間被占滿的現象為:在執行增刪改語句(產生事務日誌)的過程中提示,The transaction log for database '*** ...
  • 案例環境: 操作系統: Windows 2003 SE 32bit(SP2) 資料庫版本:Microsoft SQL Server 2005 - 9.00.5069.00 (Intel X86) Aug 22 2012 16:01:52 Copyright (c) 1988-2005 Microso... ...
  • MySQL - 事務 在學習事務這一概念前,我們需要需要構思一個場景 場景構思 假設該場景發生於一個銀行轉賬背景下,月中,又到了發工資的日子。潭州教育科技集團打算給Tuple老師發放一個月的工資。(此處,我們假設轉賬都是由人工操作的),整個過程本應該如下: 公司財務核對Tuple老師工資單 確認公司 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...