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快速开发套件

	@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中
			HbaseQuery query = new HbaseQuery(params)
					.buildScanCount().buildPageRowKey(token)
					.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中把这两个参数直接封装了。

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 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);

    public HbaseQuery(Map<String, Object> params)throws  Exception{
        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");

    }
    public HbaseQuery buildScanCount()throws  Exception{
        countScan= new Scan();
        countScan.setMaxVersions();
        countScan.setCaching(5);
        Long startLong = Long.MAX_VALUE-startDate.getTime();
        countScan.setStopRow((startLong+"-").getBytes());

        Long endLong = Long.MAX_VALUE-(endDate.getTime()-1);

        countScan.setStartRow((endLong+"-").getBytes());
        return this;
    }

    public HbaseQuery buildPageRowKey(String token)throws Exception{
        dataScan.setMaxVersions();

        Long startLong = Long.MAX_VALUE-startDate.getTime();
        dataScan.setStopRow((startLong+"-").getBytes());
        Long endLong = Long.MAX_VALUE-(endDate.getTime()-1);
        dataScan.setStartRow((endLong+"-").getBytes());

        RedisUtils redisUtils = (RedisUtils)SpringContextUtils.getBean("redisUtils");
        List<String> pageStartRowKeys = redisUtils.get(token,List.class);

        //点击上一页或下一页
        if(params.get("pageicon")!=null&&!((String)params.get("pageicon")).equals("")){
            //且redis中的startRowKeys不为空
            String pageicon =  (String)params.get("pageicon");
            if(pageStartRowKeys!=null){
                String startRowKey = pageStartRowKeys.get(this.page-1);
                if(pageicon.equals("next")&&pageStartRowKeys.size()==this.page){
                    dataScan.setStartRow(startRowKey.getBytes());
                    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);
                    dataScan.setStartRow(startRowKey.getBytes());
                    dataScan.setStopRow(stopRowKey.getBytes());
                    Filter pageFilter=new PageFilter(this.getLimit());
                    fl.addFilter(pageFilter);
                }

            }else{
                throw new Exception("点的是分页,但是redis中没有数据,这程序肯定有问题");
            }
        }else{//点击的非分页按钮,则删除redis中分页信息
            redisUtils.delete(token);
            Filter pageFilter=new PageFilter(this.getLimit()+1);
            fl.addFilter(pageFilter);
        }
        dataScan.setCaching(cache);
        return this;
    }

    public HbaseQuery buildDataFilter(Filter filter){
        fl.addFilter(filter);
        return this;
    }
    public HbaseQuery buildCountFilter(Filter filter){
        fl.addFilter(filter);
        return this;
    }
    public HbaseQuery finish(){
        countScan.setFilter(countfl);
        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时特别注意。

如有什么更好的办法或代码缺陷,欢迎留言探讨。

时间: 2024-10-11 17:44:25

hbase+springboot+redis实现分页的相关文章

Hadoop、Spark、HBase与Redis的适用性讨论(全文)

最近在网上又看到有关于Hadoop适用性的讨论[1].想想今年大数据技术开始由互联网巨头走向中小互联网和传统行业,估计不少人都在考虑各种"纷繁复杂"的大数据技术的适用性的问题.这儿我就结合我这几年在Hadoop等大数据方向的工作经验,与大家讨论一下Hadoop.Spark.HBase及Redis等几个主流大数据技术的使用场景(首先声明一点,本文中所指的Hadoop,是很"狭义"的Hadoop,即在HDFS上直接跑MapReduce的技术,下同). 我这几年实际研究和

HBase、Redis、MongoDB、Couchbase、LevelDB主流 NoSQL 数据库的对比

HBase.Redis.MongoDB.Couchbase.LevelDB主流 NoSQL 数据库的对比 最近小组准备启动一个 node 开源项目,从前端亲和力.大数据下的IO性能.可扩展性几点入手挑选了 NoSql 数据库,但具体使用哪一款产品还需要做一次选型. 我们最终把选项范围缩窄在 HBase.Redis.MongoDB.Couchbase.LevelDB 五款较主流的数据库产品中,本文将主要对它们进行分析对比. 鉴于缺乏项目中的实战经验沉淀,本文内容和观点主要还是从各平台资料搜罗汇总,

MongoDB、Hbase、Redis等NoSQL优劣势、应用场景

NoSQL的四大种类 NoSQL数据库在整个数据库领域的江湖地位已经不言而喻.在大数据时代,虽然RDBMS很优秀,但是面对快速增长的数据规模和日渐复杂的数据模型,RDBMS渐渐力不从心,无法应对很多数据库处理任务,这时NoSQL凭借易扩展.大数据量和高性能以及灵活的数据模型成功的在数据库领域站稳了脚跟. 目前大家基本认同将NoSQL数据库分为四大类:键值存储数据库,文档型数据库,列存储数据库和图形数据库,其中每一种类型的数据库都能够解决关系型数据不能解决的问题.在实际应用中,NoSQL数据库的分

redis实现分页

redis实现分页功能,主要是将数据缓存起来,无需频繁查询数据库,减少数据库的压力. 适用场景:单用户操作列表界面分页,如博客列表. 缺点:不可模糊查询,缺少灵活性. 封装类: class XgRedis { protected $_redis; public function __construct($hash_prefix=''){ $this->_redis = connectRedis::getinstance();; //$this->_redis = Redis::connecti

Springboot + redis + 注解 + 拦截器来实现接口幂等性校验

Springboot + redis + 注解 + 拦截器来实现接口幂等性校验 1. SpringBoot 整合篇 2. 手写一套迷你版HTTP服务器 3. 记住:永远不要在MySQL中使用UTF-8 4. Springboot启动原理解析 一.概念 幂等性, 通俗的说就是一个接口, 多次发起同一个请求, 必须保证操作只能执行一次比如: 订单接口, 不能多次创建订单 支付接口, 重复支付同一笔订单只能扣一次钱 支付宝回调接口, 可能会多次回调, 必须处理重复回调 普通表单提交接口, 因为网络超时

springboot+redis实现session共享

1.场景描述 因项目访问压力有点大,需要做负载均衡,但是登录使用的是公司统一提供的单点登录系统,需要做session共享,否则假如在A机器登录成功,在B机器上操作就会存在用户未登录情况. 2. 解决方案 因项目是springboot项目,采用Springboot+Springsession+Redis来实现session共享. 2.1 pom.xml文件 <dependency> <groupId>org.springframework.boot</groupId> &

HBase多条件及分页查询的一些方法

HBase是Apache Hadoop生态系统中的重要一员,它的海量数据存储能力,超高的数据读写性能,以及优秀的可扩展性使之成为最受欢迎的NoSQL数据库之一.它超强的插入和读取性能与它的数据组织方式有着密切的关系,在逻辑上,HBase的表数据按RowKey进行字典排序, RowKey实际上是数据表的一级索引(Primary Index),由于HBase本身没有二级索引(Secondary Index)机制,基于索引检索数据只能单纯地依靠RowKey.也只有使用RowKey查询数据才能得到非常高

springboot redis 缓存对象

只要加入spring-boot-starter-data-redis , springboot 会自动识别并使用redis作为缓存容器,使用方式如下 gradle加入依赖 compile("org.springframework.boot:spring-boot-starter-data-redis:${springBootVersion}") redis configuration 中启用缓存 @Configuration @EnableCaching public class Re

Springboot+redis 整合

运行环境: JDK1.7. SpringBoot1.4.7 redis3.0.4 1.生成Springboot项目,分别添加web,redis依赖,具体的maven依赖如下 1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-data-redis</artifactId> 4 </dependency&g