hbase+springboot+redis实现分页
实现原理:
1、读取hbase数据每页的数据时多取一条数据。如:分页是10条一页,第一次查询hbase时, 取10+1条数据,然后把第一条和最后一条rowkey数据保存在redis中,redis中的key为用户的token+URL。即token.set(token+url:list<String>);
2、前台点击下页时,查询当前页(currentPagae)在redis的list是否存在list.get(currentPage)的rowkey。如果存在,则以之前为startRowKey,取10+1条,并把最后一条保存在redis中。不存在则查询出错,提示重新查询,理论上不会出现,除非redis挂了。
3、如果查询的数据的startRowKey和stopRowKey在token中都能找到。则只需要查询这个范围数据即可。
3、什么时候清除这些redis数据呢?第一、设置redis有效期。第二,这条很重要,是保证前三条数据准确性的前提。在用户点击非下一页上一页按钮的操作时,都清除redis中的当前用户的数据。
4、即然有分页,那当然有数据量统计count,这个我使用hbase的协处理器coprocessor。当然每次查询count也费时间。当第一次查询时,把count保存在用户的redis中。redis的清除还是第(3)步的过程。
5、能这么做还有一个很重要的前提:前端界面的分页,只提供了两个按钮:上一页和下一页。这是保证这个方案可行性的基础。
下面上代码:
controller中方法的代码,这里的基本框架使用的是renren快速开发套件
@ResponseBody
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params,HttpServletRequest httpRequest){ long time = System.currentTimeMillis();
HBasePage page= null;
try{
String token = httpRequest.getHeader("token"); //如需要额外的过滤器,请自己定义并使用.buildFilter(filter)方法添加到query中
//请注意顺序:如果有RowKey的过滤,先添加buildRowKeyFilter,再加buildRowKeyPage。否则无效
HbaseQuery query = new HbaseQuery(params,token,true); Object cnOrderCodeObject = params.get("cnOrderCode");
if(cnOrderCodeObject!=null&&!((String)cnOrderCodeObject).equals("")){
String cnOrderCode = (String)cnOrderCodeObject;
query.buildRowKeyFilter(cnOrderCode,cnOrderCode);
}
query.buildRowKeyPage().finish();
page = service.query(query).buildRedisRowKey(token); }catch (Exception e){
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
System.out.println("time2-time==list="+(time2-time));
return R.ok().put("page",page); }
处理查询参数的类HBaseQuery,因为业务原因,所有的hbase查询有两个必须的条件:开始日期和结束日期,所以我在HBaseQuery中把这两个参数直接封装了。
@Data
public class HbaseQuery {
private static final long serialVersionUID = 1L;
//当前页码
private int page;
//每页条数
private long limit; private Map<String, Object> params; private List<String> pageStartRowKeys; private Date startDate; private Date endDate; private StringBuffer startRowKey = new StringBuffer();
private StringBuffer stopRowKey = new StringBuffer(); private RedisUtils redisUtils = (RedisUtils)SpringContextUtils.getBean("redisUtils"); private Scan countScan; private Scan dataScan= new Scan(); private int cache = 10; private DateFormat sf =new SimpleDateFormat("yyyy-MM-dd"); private FilterList fl = new FilterList(FilterList.Operator.MUST_PASS_ALL);
private FilterList countfl = new FilterList(FilterList.Operator.MUST_PASS_ALL); private boolean isOpenCount;
private String token; public HbaseQuery(Map<String, Object> params,String token,boolean isOpenCount)throws Exception{
this.isOpenCount = isOpenCount;
this.token = token;
this.params = params;
String temp2 = (String)params.get("startDate");
startDate = sf.parse(temp2);
String temp = (String)params.get("endDate");
endDate = sf.parse(temp);
endDate = DateUtils.addDays(endDate,1);
this.page = Integer.parseInt(params.get("page").toString());
this.limit = Integer.parseInt(params.get("limit").toString());
cache = limit>5000?5000:((int)limit+1);//加1,因为每次都会取limit+1条数据
params.remove("startDate");
params.remove("endDate");
params.remove("page");
params.remove("limit"); initRowKeyFilter();
} public void initRowKeyFilter(){
Long endLong = Long.MAX_VALUE-startDate.getTime();
Long startLong = Long.MAX_VALUE-(DateUtils.addDays(endDate,1).getTime()-1);
stopRowKey.append(endLong);
startRowKey.append(startLong);
} //请注意顺序:如果有RowKey的过滤,先添加buildRowKeyFilter,再加buildRowKeyPage。否则无效
public HbaseQuery buildRowKeyFilter(String startRowKey,String stopRowKey){
if (this.startRowKey.equals("")) {
this.stopRowKey.append(startRowKey);
} else {
this.stopRowKey.append("-").append(startRowKey);
}
if (this.stopRowKey.equals("")) {
this.stopRowKey.append(stopRowKey);
} else {
this.stopRowKey.append("-").append(stopRowKey);
}
return this;
} //请注意顺序:如果有RowKey的过滤,先添加buildRowKeyFilter,再加buildRowKeyPage。否则无效
public HbaseQuery buildRowKeyPage()throws Exception{ List<String> pageStartRowKeys = redisUtils.get(token,List.class); //点击上一页或下一页
Object temp =params.get("pageicon");
if(temp!=null&&!((String)temp).equals("")&&!((String)temp).equals("records")){
//且redis中的startRowKeys不为空
String pageicon = (String)temp;
if(pageStartRowKeys!=null){
String startRowKey = pageStartRowKeys.get(this.page-1);
if(pageicon.equals("next")&&pageStartRowKeys.size()==this.page){
this.startRowKey = new StringBuffer(startRowKey);
Filter pageFilter=new PageFilter(cache);
fl.addFilter(pageFilter);
}else if((pageicon.equals("next")&&pageStartRowKeys.size()>this.page)
||pageicon.equals("prev")){
String stopRowKey = pageStartRowKeys.get(this.page);
this.startRowKey = new StringBuffer(startRowKey);
this.stopRowKey = new StringBuffer(stopRowKey);
Filter pageFilter=new PageFilter(this.getLimit());
fl.addFilter(pageFilter);
} }else{
throw new Exception("点的是分页,但是redis中没有数据,这程序肯定有问题");
}
}else{//点击的非分页按钮,则删除redis中分页信息
redisUtils.delete(token);
redisUtils.delete(RedisKeys.getPageCountKey(token));
Filter pageFilter=new PageFilter(this.getLimit()+1);
fl.addFilter(pageFilter);
}
dataScan.setCaching(cache);
return this;
} public HbaseQuery addFilter(Filter filter){
fl.addFilter(filter);
countfl.addFilter(filter);
return this;
} public HbaseQuery finish(){
String count = redisUtils.get(RedisKeys.getPageCountKey(token));
if(isOpenCount&&(count==null||count.equals(""))){
countScan= new Scan();
countScan.setMaxVersions();
countScan.setCaching(5);
countScan.setStartRow((startRowKey.toString()).getBytes());
countScan.setStopRow((stopRowKey.toString()).getBytes());
countScan.setFilter(countfl);
}
dataScan.setMaxVersions();
dataScan.setStartRow((startRowKey.toString()).getBytes());
dataScan.setStopRow((stopRowKey.toString()).getBytes());
dataScan.setFilter(fl); return this;
} }
查询hbase的方法。注意:在使用hbase的协处理器前,请先确保表开通了此功能。
hbase表开通协处理功能方法(shell命令):
(1)disable指定表。hbase> disable 'mytable'
(2)添加aggregation hbase> alter 'mytable', METHOD => 'table_att','coprocessor'=>'|org.apache.hadoop.hbase.coprocessor.AggregateImplementation||'
(3)重启指定表 hbase> enable 'mytable'
public HBasePage query(HbaseQuery query) {
long time = System.currentTimeMillis();
Map<String,String> communtiyKeysMap = new HashMap<>();
HBasePage page = new HBasePage(query.getLimit(),query.getPage());
final String tableName = this.getTableName();
if(query.getCountScan()!=null){
AggregationClient ac = new AggregationClient(hbaseTemplate.getConfiguration());
try{
long count = ac.rowCount(TableName.valueOf(tableName),new LongColumnInterpreter(),query.getCountScan());
page.setTotalCount(count);
}catch (Throwable e){
e.printStackTrace();
}
}
long time2 = System.currentTimeMillis();
List rows = hbaseTemplate.find(tableName, query.getDataScan(), new RowMapper<Object>() {
@Override
public Object mapRow(Result result, int i) throws Exception {
Class clazz = ReflectMap.get(tableName);//这里做了表名和实体Bean的映射。
if(i==0){
communtiyKeysMap.put("curPageStart", new String(result.getRow()));
}
if(i==query.getLimit()){
communtiyKeysMap.put("nextPageStart", new String(result.getRow()));
}
HBaseResultBuilder hrb = new HBaseResultBuilder<Object>("sf", result, clazz);
return hrb.buildAll().fetch();
}
});
//
if(rows.size()>0&&page.getPageSize()<rows.size()){
rows.remove(rows.size()-1);
}
page.setList(rows);
page.setNextPageRow(communtiyKeysMap.get("nextPageStart"));
page.setCurPageRow(communtiyKeysMap.get("curPageStart"));
long time3 = System.currentTimeMillis();
System.out.println("time2-time==getCount="+(time2-time));
System.out.println("time3-time2==getData="+(time3-time2));
return page;
}
/分页类的代码HbasePage
public class HBasePage implements Serializable {
private static final long serialVersionUID = 1L;
//总记录数
protected long totalCount;
//每页记录数
protected long pageSize;
//总页数
protected int totalPage;
//当前页数
protected int currPage;
//列表数据
protected List<Object> list;
private String nextPageRow;//下一页的ROWKEY
private String curPageRow;//当前页的开始ROWKEY
/**
* 分页
* @param pageSize 每页记录数
* @param currPage 当前页数
*/
public HBasePage(long pageSize, int currPage) {
this.list = list;
this.totalCount = totalCount;
this.pageSize = pageSize;
this.currPage = currPage;
}
public void setTotalCount(long totalCount) {
this.totalCount = totalCount;
this.totalPage = (int)Math.ceil((double)totalCount/pageSize);
}
public HBasePage buildRedisRowKey(String token){
RedisUtils redisUtils = (RedisUtils)SpringContextUtils.getBean("redisUtils");
List<String> pageStartRowKeys = redisUtils.get(token,List.class);
List<String> pageRowKeys = redisUtils.get(token,List.class);
if(this.getList().size()>0){
if(pageRowKeys==null||pageRowKeys.size()<=0){
pageRowKeys = new ArrayList<>();
pageRowKeys.add(this.getCurPageRow().substring(0,this.getCurPageRow().indexOf("-")+1));
pageRowKeys.add(this.getNextPageRow().substring(0,this.getNextPageRow().indexOf("-")+1));
redisUtils.set(token,pageRowKeys);
}else{
if(pageRowKeys.size()>this.getCurrPage()){
//doNothing
}else if(pageRowKeys.size()==this.getCurrPage()){
pageRowKeys.add(this.getNextPageRow().substring(0,this.getNextPageRow().indexOf("-")+1));
redisUtils.set(token,pageRowKeys);
}
}
}
return this;
}
}
注意:
1、我的rowKey设置规则是 (Long_Max-new Date().getTime()+"-"+id),所以在看startRowKey和stopRowKey时特别注意。
如有什么更好的办法或代码缺陷,欢迎留言探讨。
@ResponseBody
@RequestMapping("/list")
@RequiresPermissions("business:stockinbill:list")
public R list(@RequestParam Map<String, Object> params,HttpServletRequest httpRequest){ long time = System.currentTimeMillis();
HBasePage page= null;
try{
String token = httpRequest.getHeader("token"); //如需要额外的过滤器,请自己定义并使用.buildFilter(filter)方法添加到query中
//请注意顺序:如果有RowKey的过滤,先添加buildRowKeyFilter,再加buildRowKeyPage。否则无效
HbaseQuery query = new HbaseQuery(params,token,true); Object cnOrderCodeObject = params.get("cnOrderCode");
if(cnOrderCodeObject!=null&&!((String)cnOrderCodeObject).equals("")){
String cnOrderCode = (String)cnOrderCodeObject;
query.buildRowKeyFilter(cnOrderCode,cnOrderCode);
}
query.buildRowKeyPage().finish();
page = stockInBillService.query(query).buildRedisRowKey(token); }catch (Exception e){
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
System.out.println("time2-time==list="+(time2-time));
return R.ok().put("page",page); }
hbase+springboot+redis实现分页的更多相关文章
- HBase、Redis、MongoDB、Couchbase、LevelDB主流 NoSQL 数据库的对比
最近小组准备启动一个 node 开源项目,从前端亲和力.大数据下的IO性能.可扩展性几点入手挑选了 NoSql 数据库,但具体使用哪一款产品还需要做一次选型. 我们最终把选项范围缩窄在 HBase.R ...
- 补习系列(14)-springboot redis 整合-数据读写
目录 一.简介 二.SpringBoot Redis 读写 A. 引入 spring-data-redis B. 序列化 C. 读写样例 三.方法级缓存 四.连接池 小结 一.简介 在 补习系列(A3 ...
- SpringBoot+Redis整合
SpringBoot+Redis整合 1.在pom.xml添加Redis依赖 <!--整合Redis--> <dependency> <groupId>org.sp ...
- redis实现分页
redis实现分页功能,主要是将数据缓存起来,无需频繁查询数据库,减少数据库的压力. 适用场景:单用户操作列表界面分页,如博客列表. 缺点:不可模糊查询,缺少灵活性. 封装类: class XgRed ...
- springboot +redis配置
springboot +redis配置 pom依赖 <dependency> <groupId>org.springframework.boot</groupId> ...
- MongoDB、Hbase、Redis等NoSQL优劣势、应用场景
NoSQL的四大种类 NoSQL数据库在整个数据库领域的江湖地位已经不言而喻.在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数 ...
- 【springboot】【redis】springboot+redis实现发布订阅功能,实现redis的消息队列的功能
springboot+redis实现发布订阅功能,实现redis的消息队列的功能 参考:https://www.cnblogs.com/cx987514451/p/9529611.html 思考一个问 ...
- MongoDB、Hbase、Redis等NoSQL分析
NoSQL的四大种类 NoSQL数据库在整个数据库领域的江湖地位已经不言而喻.在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数 ...
- spring boot 学习(十四)SpringBoot+Redis+SpringSession缓存之实战
SpringBoot + Redis +SpringSession 缓存之实战 前言 前几天,从师兄那儿了解到EhCache是进程内的缓存框架,虽然它已经提供了集群环境下的缓存同步策略,这种同步仍然需 ...
随机推荐
- Extjs2.0 desktop 动态创建桌面图标和开始菜单
这几天一直纠结Extjs desktop怎么动态读取数据,用Ext.net已经实现但是不灵活.Ext.net做出来的桌面在窗口关闭后只是隐藏该窗口,并没有释放,对于我这种js菜鸟来说,改那一坨代码要人 ...
- hadoop2.0的数据副本存放策略
在hadoop2.0中,datanode数据副本存放磁盘选择策略有两种方式: 第一种是沿用hadoop1.0的磁盘目录轮询方式,实现类:RoundRobinVolumeChoosingPolicy.j ...
- DeepLearning.ai学习笔记(二)改善深层神经网络:超参数调试、正则化以及优化--Week2优化算法
1. Mini-batch梯度下降法 介绍 假设我们的数据量非常多,达到了500万以上,那么此时如果按照传统的梯度下降算法,那么训练模型所花费的时间将非常巨大,所以我们对数据做如下处理: 如图所示,我 ...
- Angular2响应式表单
本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...
- hdu3695 ac自动机入门
Computer Virus on Planet Pandora Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 256000/1280 ...
- PHP程序员40点陋习
1.不写注释 2.不使用可以提高生产效率的IDE工具 3.不使用版本控制 4.不按照编程规范写代码 5.不使用统一的方法 6.编码前不去思考和计划 7.在执行sql前不执行编码和安全检测 8.不使用测 ...
- oracle基本查询语句总结
spool E:\基本查询.txt 将命令行的语句写入到指定的目下的指定的文件中 host cls 清屏命令 show user 显示当前操作的用户 desc emp 查看表结构 select * f ...
- 浅谈Java接口
接口(英文:Interface)是Java中非常重要的内容,初学的时候可能感受不深,但是在做项目的时候,对面向接口编程的运用就变得尤为重要,不过这是后话了.现在先讨论假如是刚刚接触接口这个概念,该怎么 ...
- 调试 ms 源代码
如果需要调试 WPF 源代码或框架源代码,那么需要使用 DotPeek. 首先需要下载 dotPeek ,可以到官网下载 dotPeek: Free .NET Decompiler & Ass ...
- linux 守护进程编程
概述: Daemon运行在后台也称作"后台服务进程". 它是没有控制终端与之相连的进程.它独立于控制终端.通常周期的执行某种任务. 守护进程脱离终端是为了避免进程在执行过程中的信息 ...