前言

  接着上篇博客,我们来谈谈java操作cassandra分页,需要注意的是这个分页与我们平时所做的页面分页是不同的,具体有啥不同,大家耐着性子往下看。

  上篇博客讲到了cassandra的分页,相信大家会有所注意:下一次的查询依赖上一次的查询(上一次查询的最后一条记录的全部主键),不像mysql那样灵活,所以只能实现上一页、下一页这样的功能,不能实现第多少页那样的功能(硬要实现的话性能就太低了)。

  我们先看看驱动官方给的分页做法

  如果一个查询得到的记录数太大,一次性返回回来,那么效率非常低,并且很有可能造成内存溢出,使得整个应用都奔溃。所以了,驱动对结果集进行了分页,并返回适当的某一页的数据。

  路漫漫其修远兮,吾将上下而求索!

  github:https://github.com/youzhibing

  码云(gitee):https://gitee.com/youzhibing

设置抓取大小

  抓取大小指的是一次从cassandra获取到的记录数,换句话说,就是每一页的记录数;我们能够在创建cluster实例的时候给它的fetch size指定一个默认值,如果没有指定,那么默认是5000

  1. // At initialization:
  2. Cluster cluster = Cluster.builder()
  3. .addContactPoint("127.0.0.1")
  4. .withQueryOptions(new QueryOptions().setFetchSize(2000))
  5. .build();
  6.  
  7. // Or at runtime:
  8. cluster.getConfiguration().getQueryOptions().setFetchSize(2000);

  另外,statement上也能设置fetch size

  1. Statement statement = new SimpleStatement("your query");
  2. 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;

  以下是如何使用这些方法提前预取下一页,以避免在某一页迭代完后才抓取下一页造成的性能下降:

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

保存并重新使用分页状态

  有时候,将分页状态保存起来,对以后的恢复是非常有用的,想象一下:有一个无状态Web服务,显示结果列表,并显示下一页的链接,当用户点击这个链接的时候,我们需要执行与之前完全相同的查询,除了迭代应该从上一页停止的位置开始;相当于记住了上一页迭代到了哪了,那么下一页从这里开始即可。

  为此,驱动程序会暴露一个PagingState对象,该对象表示下一页被提取时我们在结果集中的位置。

  1. ResultSet resultSet = session.execute("your query");
  2. // iterate the result set...
  3. PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
  4. // PagingState对象可以被序列化成字符串或字节数组
  5. String string = pagingState.toString();
  6. byte[] bytes = pagingState.toBytes();

  PagingState对象被序列化后的内容可以持久化存储起来,也可用作分页请求的参数,以备后续再次被利用,反序列化成对象即可:

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

  请注意,分页状态只能使用完全相同的语句重复使用(相同的查询,相同的参数)。而且,它是一个不透明的值,只是用来存储一个可以被重新使用的状态值,如果尝试修改其内容或将其使用在不同的语句上,驱动程序会抛出错误。

  具体我们来看下代码,下例是模拟页面分页的请求,实现遍历teacher表中的全部记录:

  接口:

  1. import java.util.Map;
  2.  
  3. import com.datastax.driver.core.PagingState;
  4.  
  5. public interface ICassandraPage
  6. {
  7. Map<String, Object> page(PagingState pagingState);
  8.  
  9. }

  主体代码:

  1. import java.util.ArrayList;
  2. import java.util.HashMap;
  3. import java.util.List;
  4. import java.util.Map;
  5.  
  6. import com.datastax.driver.core.PagingState;
  7. import com.datastax.driver.core.ResultSet;
  8. import com.datastax.driver.core.Row;
  9. import com.datastax.driver.core.Session;
  10. import com.datastax.driver.core.SimpleStatement;
  11. import com.datastax.driver.core.Statement;
  12. import com.huawei.cassandra.dao.ICassandraPage;
  13. import com.huawei.cassandra.factory.SessionRepository;
  14. import com.huawei.cassandra.model.Teacher;
  15.  
  16. public class CassandraPageDao implements ICassandraPage
  17. {
  18. private static final Session session = SessionRepository.getSession();
  19.  
  20. private static final String CQL_TEACHER_PAGE = "select * from mycas.teacher;";
  21.  
  22. @Override
  23. public Map<String, Object> page(PagingState pagingState)
  24. {
  25. final int RESULTS_PER_PAGE = 2;
  26. Map<String, Object> result = new HashMap<String, Object>(2);
  27. List<Teacher> teachers = new ArrayList<Teacher>(RESULTS_PER_PAGE);
  28.  
  29. Statement st = new SimpleStatement(CQL_TEACHER_PAGE);
  30. st.setFetchSize(RESULTS_PER_PAGE);
  31.  
  32. // 第一页没有分页状态
  33. if (pagingState != null)
  34. {
  35. st.setPagingState(pagingState);
  36. }
  37.  
  38. ResultSet rs = session.execute(st);
  39. result.put("pagingState", rs.getExecutionInfo().getPagingState());
  40.  
  41. //请注意,我们不依赖RESULTS_PER_PAGE,因为fetch size并不意味着cassandra总是返回准确的结果集
  42. //它可能返回比fetch size稍微多一点或者少一点,另外,我们可能在结果集的结尾
  43. int remaining = rs.getAvailableWithoutFetching();
  44. for (Row row : rs)
  45. {
  46. Teacher teacher = this.obtainTeacherFromRow(row);
  47. teachers.add(teacher);
  48.  
  49. if (--remaining == 0)
  50. {
  51. break;
  52. }
  53. }
  54. result.put("teachers", teachers);
  55. return result;
  56. }
  57.  
  58. private Teacher obtainTeacherFromRow(Row row)
  59. {
  60. Teacher teacher = new Teacher();
  61. teacher.setAddress(row.getString("address"));
  62. teacher.setAge(row.getInt("age"));
  63. teacher.setHeight(row.getInt("height"));
  64. teacher.setId(row.getInt("id"));
  65. teacher.setName(row.getString("name"));
  66.  
  67. return teacher;
  68. }
  69.  
  70. }

  测试代码:

  1. import java.util.Map;
  2.  
  3. import com.datastax.driver.core.PagingState;
  4. import com.huawei.cassandra.dao.ICassandraPage;
  5. import com.huawei.cassandra.dao.impl.CassandraPageDao;
  6.  
  7. public class PagingTest
  8. {
  9.  
  10. public static void main(String[] args)
  11. {
  12. ICassandraPage cassPage = new CassandraPageDao();
  13. Map<String, Object> result = cassPage.page(null);
  14. PagingState pagingState = (PagingState) result.get("pagingState");
  15. System.out.println(result.get("teachers"));
  16. while (pagingState != null)
  17. {
  18. // PagingState对象可以被序列化成字符串或字节数组
  19. System.out.println("==============================================");
  20. result = cassPage.page(pagingState);
  21. pagingState = (PagingState) result.get("pagingState");
  22. System.out.println(result.get("teachers"));
  23. }
  24. }
  25.  
  26. }

  我们来看看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表进行全表遍历的时候就需要用到分页,因为表中数据量太大,亿级别的数据不可能一次全部加载到内存中。

  工程附件

cassandra高级操作之分页的java实现(有项目具体需求)的更多相关文章

  1. cassandra高级操作之索引、排序以及分页

    本次就给大家讲讲cassandra的高级操作:索引.排序和分页:处于性能的考虑,cassandra对这些支持都比较简单,所以我们不能希望cassandra完全适用于我们的逻辑,而是应该将我们的逻辑设计 ...

  2. cassandra高级操作之JMX操作

    需求场景 项目中有这么个需求:统计集群中各个节点的数据量存储大小,不是记录数. 一开始有点无头绪,后面查看cassandra官方文档看到Monitoring章节,里面说到:Cassandra中的指标使 ...

  3. 高级软件工程2017第5次作业—— 团队项目:需求改进&系统设计

    Deadline:2017-10-23(周一) 21:00pm 注:以下内容参考 集大作业 1.评分规则: 按时交 - 有分,检查的项目包括后文的四个方面 需求&原型改进 - 20分 系统设计 ...

  4. SpringMVC整合Mongodb开发,高级操作

    开发环境: 操作系统:windows xpMongodb:2.0.6依 赖 包:Spring3.2.2 + spring-data-mongodb-1.3.0 + Spring-data-1.5 +  ...

  5. 基于CDH5.x 下面使用eclipse 操作hive 。使用java通过jdbc连接HIVESERVICE 创建表

    基于CDH5.x 下面使用eclipse 操作hive .使用java通过jdbc连接HIVESERVICE 创建表 import java.sql.Connection; import java.s ...

  6. Eclipse高级操作 远程调试

    Eclipse高级操作 远程调试 JPDA是SUN JDK自带的远程调试机制.它提供了一套标准的调试接口,可以从虚拟机一级允许外界用特定协议探测虚拟机内部的运作细节.只要你装了JDK1.2以上的SUN ...

  7. 对象流,它们是一对高级流,负责即将java对象与字节之间在读写的过程中进行转换。 * java.io.ObjectOutputStream * java.io.ObjectInputStream

    package seday06; import java.io.Serializable;import java.util.Arrays; /** * @author xingsir * 使用当前类来 ...

  8. html(四)数据库curd操作与分页查询

    数据库操作curd : 1.首先要建立项目处理好自己逻辑包: 其中util工具包中建立两个工具类 jdbc连接和page分页 DBUtil.java: db工具类就是用于连接数据库的jdbc架包,里面 ...

  9. MySQL之高级操作

     新增数据: 基本语法: insert into 表名 [(字段列表)] values(列表值) 在数据插入的时候,假设主键对应的值已经存在,插入一定会失败 主键冲突: 当主键存在冲突的时候(Dupl ...

随机推荐

  1. (转)java二维数组的深度学习(静态与动态)

    转自:http://developer.51cto.com/art/200906/128274.htm,谢谢 初始化: 1.动态初始化:数组定义与为数组分配空间和赋值的操作分开进行:2.静态初始化:在 ...

  2. 用9种办法解决 JS 闭包经典面试题之 for 循环取 i

    2017-01-06 Tomson JavaScript 转自 https://segmentfault.com/a/1190000003818163 闭包 1.正确的说,应该是指一个闭包域,每当声明 ...

  3. Linux下connect超时处理【总结】

    1.前言 最近在写一个测试工具,要求快速的高效率的扫描出各个服务器开放了哪些端口.当时想了一下,ping只能检测ip,判断服务器的网络是连通的,而不能判断是否开放了端口.我们知道端口属于网络的应用层, ...

  4. spring入门--Spring框架底层原理

    上一篇的博客,我们可以看出来,spring可以维护各个bean (对象),并向其中注入属性值.那么,如果们要把一个对象的引用注入另外一个对象呢?应该怎么处理呢? 我们知道,对于对象中的属性来说,我们注 ...

  5. Hadoop1.0.3环境搭建流程

    0x00 大数据平台相关链接 官网:http://hadoop.apache.org/ 主要参考教程:http://www.cnblogs.com/xia520pi/archive/2012/05/1 ...

  6. css中书写小三角

    我们在开发过程中,有很多的方向标签不是图片,而是用css方法书写上去的. 首先我们要了解原理,border的边框的脚步是45度角. 向左方向: width:0px: height:0px: borde ...

  7. BZOJ 3412: [Usaco2009 Dec]Music Notes乐谱(离线处理)

    这道题貌似怎么写都可以吧= =,我先读入询问然后从小到大处理就行了= = PS:水水题真的好!无!聊!但是好!欢!乐! CODE: #include<cstdio>#include< ...

  8. SQL SERVER的事务日志

    1 基本介绍 每个数据库都具有事务日志,用于记录所有事物以及每个事物对数据库所作的操作. 日志的记录形式需要根据数据库的恢复模式来确定,数据库恢复模式有三种: 完整模式,完全记录事物日志,需要定期进行 ...

  9. observe.js 源码 学习笔记

    /** * observejs --- By dnt http://kmdjs.github.io/ * Github: https://github.com/kmdjs/observejs * MI ...

  10. 2818: Gcd

    2818: Gcd Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2170  Solved: 979[Submit][Status][Discuss] ...