java MongoDB分页优化

最近项目在做网站用户数据新访客统计,数据存储在MongoDB中,统计的数据其实也并不是很大,1000W上下,但是公司只配给我4G内存的电脑,让我程序跑起来气喘吁吁...很是疲惫不堪。

最常见的问题莫过于查询MongoDB内存溢出,没办法只能分页查询。这种思想大家可能都会想到,但是如何分页,确实多有门道!

网上用的最多的,也是最常见的分页采用的是skip+limit这种组合方式,这种方式对付小数据倒也可以,但是对付上几百上千万的大数据,却只能望而兴叹...

经过网上各种查找资料,寻师问道的,发现了一种速度足以把skip+limit组合分页甩出几条街的方法。

思路: 条件查询+排序+限制返回记录。边查询,边排序,排序之后,抽取第一次分页中的最后一条记录,作为第二次分页的条件,进行条件查询,以此类推....

    先上代码:

  /**
	 * 小于指定日期的所有根据UUID分组的访问记录
	 * @param 指定日期
	 * @return 所有访问记录的MAP
	 */
	public static Multimap<String, Map<String, String>> getOldVisitors(String date){

		//每次查询的记录数
		int pagesize = 100000;

		//mongodb中的"_id"
		String objectId = "";

		//方法的返回值类型,此处用的google guava
		Multimap<String, Map<String, String>> mapless = null;

		//查询的条件
		BasicDBObject queryless = new BasicDBObject(),fields = new BasicDBObject(),field = new BasicDBObject();

		//初始化返回的mongodb集合操作对象,大家可以写个数据连接池
		dbCol = init();

		//查询指定字段,字段越少,查询越快,当然都是一些不必要字段
		field.put("uuid",1);

		fields.put("uuid", 1);

		fields.put("initTime", 1);

		//小于指定日期的条件
		String conditionless = TimeCond.getTimeCondless(date);

		queryless.put("$where", conditionless);

		DBCursor cursorless = dbCol.find(queryless,field);

		//MongoDB在小于指定日期条件下,集合总大小
		int countless = cursorless.count();

		//查询遍历的次数 circleCountless+1
		int circleCountless = countless/pagesize;

		//取模,这是最后一次循环遍历的次数
		int modless = countless%pagesize;

		//开始遍历查询
		for (int i = 1; i <=circleCountless+1; i++) {

			//文档对象
			DBObject obj = null;

			//将游标中返回的结果记录到list集合中,为什么放到list集合中?这是为后面guava 分组做准备
			List<Map<String, String>> listOfMaps = new ArrayList();

			//如果条件不为空,则加上此条件,构成多条件查询,这一步是分页的关键
			if (!"".equals(objectId)) {

	              //我们通过文档对象obj.get("_id")返回的是不带ObjectId(),所以要求此步骤
                               ObjectId id = new ObjectId(objectId);

			       queryless.append("_id", new BasicDBObject("$gt",id));

			}

			if (i<circleCountless+1) {

			cursorless = dbCol.find(queryless,fields).sort(new BasicDBObject("_id", 1)).limit(pagesize);

			}else if(i==circleCountless+1){//最后一次循环

				cursorless = dbCol.find(queryless,fields).limit(modless);
			}

		            //将游标中返回的结果记录到list集合中,为什么放到list集合中?这是为后面guava 分组做准备
			    while (cursorless.hasNext()) {

					obj = cursorless.next();

					listOfMaps.add((Map<String, String>) obj);

				}
			    //获取一次分页中最后一条记录的"_id",然后作为条件传入到下一个循环中
			    if (null!=obj) {

			    	 objectId = obj.get("_id").toString();

		            }
	        //第一次分组,根据uuid分组,分组除今天之外的历史数据
		mapless = Multimaps.index(
	                  listOfMaps,new Function<Map<String, String>, String>() {
	                      public String apply(final Map<String, String> from) {

	                    		  return from.get("uuid");
	                  }
	             });

	  	}	

		return mapless;
	}

本人初来乍到,写出的代码自然不是那么优雅难懂,况且注释写的自认为还是比较清楚,如果大家有疑问的,或者有更优雅的办法可以留言评论...

这里为什么要用"_id"这个字段作为分页的条件?其实,我也用过其他字段,比如时间字段,时间字符串也是可以比大小的,但它的效率远不如"_id"高。

关于MongoDB中的"_id",以前一直忽略它的作用,直接结果是让我耗了很多时间和精力,绕了大半圈,又回到了原点,有一种众里寻他千百度,蓦然回首,那人却在灯火阑珊处的感觉...

  MongoDB ObjectId

“4e7020cb7cac81af7136236b”这个24位的字符串,虽然看起来很长,也很难理解,但实际上它是由一组十六进制的字符构成,每个字节两位的十六进制数字,总共用了12字节的存储空间。相比MYSQLint类型的4个字节,MongoDB确实多出了很多字节。不过按照现在的存储设备,多出来的字节应该不会成为什么瓶颈。不过MongoDB的这种设计,体现着空间换时间的思想。官网中对ObjectId的规范,如图所示:

1)Time

时间戳。将刚才生成的objectid的前4位进行提取“4e7020cb”,然后按照十六进制转为十进制,变为“1315971275”,这个数字就是一个时间戳。通过时间戳的转换,就成了易看清的时间格式。

2)Machine

机器。接下来的三个字节就是“7cac81”,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectId中间的字符串都是一模一样的原因。

3)PID

进程ID。上面的Machine是为了确保在不同机器产生的objectId不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectId不冲突,接下来的“af71”两位就是产生objectId的进程标识符。

4)INC

自增计数器。前面的九个字节是保证了一秒内不同机器不同进程生成objectId不冲突,这后面的三个字节“36236b”是一个自动增加的计数器,用来确保在同一秒内产生的objectId也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。

总的来看,objectId的前4个字节时间戳,记录了文档创建的时间;接下来3个字节代表了所在主机的唯一标识符,确定了不同主机间产生不同的objectId;后2个字节的进程id,决定了在同一台机器下,不同mongodb进程产生不同的objectId;最后通过3个字节的自增计数器,确保同一秒内产生objectId的唯一性。ObjectId的这个主键生成策略,很好地解决了在分布式环境下高并发情况主键唯一性问题,值得学习借鉴。

java MongoDB分页优化

时间: 2024-10-10 04:52:53

java MongoDB分页优化的相关文章

MongoDB整理笔记のjava MongoDB分页优化

最近项目在做网站用户数据新访客统计,数据存储在MongoDB中,统计的数据其实也并不是很大,1000W上下,但是公司只配给我4G内存的电脑,让我程序跑起来气喘吁吁...很是疲惫不堪. 最常见的问题莫过于查询MongoDB内存溢出,没办法只能分页查询.这种思想大家可能都会想到,但是如何分页,确实多有门道! 网上用的最多的,也是最常见的分页采用的是skip+limit这种组合方式,这种方式对付小数据倒也可以,但是对付上几百上千万的大数据,却只能望而兴叹... 经过网上各种查找资料,寻师问道的,发现了

mongodb分页优化

mongodb分页很简单,本文主要讲分页可能遇到的问题,以及优化方案 从传统web到移动端api,我们都面临一样的问题,比如ajax get有大小显示等,都会强迫你不得不分页 比如我的项目使用ratchet做h5框架,它的push.js里就是ajax get加载其他页面,页面太大就会报错. 分页说明 以典型的列表api来说:下拉刷新是获取最新信息,然后上拉加载下一页 常见api要写的2个接口 get_latest(model,count) get_with_page(number,size) g

MongoDB分页的Java实现和分页需求的思考

前言 传统关系数据库中都提供了基于row number的分页功能,切换MongoDB后,想要实现分页,则需要修改一下思路. 传统分页思路 假设一页大小为10条.则 //page 1 1-10 //page 2 11-20 //page 3 21-30 ... //page n 10*(n-1) +1 - 10*n MongoDB提供了skip()和limit()方法. skip: 跳过指定数量的数据. 可以用来跳过当前页之前的数据,即跳过pageSize*(n-1). limit: 指定从Mon

MongoDB分页处理方案(适用于一般数据库的分页方法)

MongoDB分页处理方案(适用于一般数据库的分页方法) (2012-11-06 17:59:55) 转载▼ 标签: mongodb 分页 数据库 跳转 分类: MongoDB 转载请注明出处:http://blog.sina.com.cn/s/blog_56545fd30101442b.html MongoDB的分页性能是广大使用者所诟病的大问题之一,在大数据量环境下,如果一次跳转的页数过多,如10W多页,可能用户要等上几十秒(瞎掰的数据),有兴趣的可以去看一下这篇文章Paging & Ran

Java的性能优化

http://www.toutiao.com/i6368345864624144897/?tt_from=mobile_qq&utm_campaign=client_share&app=news_article&utm_source=mobile_qq&iid=4452590055&utm_medium=toutiao_android Java的性能优化 万能的乐乐老师 2016-12-26 17:54 Play Video 做Java基础的朋友可以加群:15449

MySQL分页优化中的“INNER JOIN方式优化分页算法”到底在什么情况下会生效?

本文出处:http://www.cnblogs.com/wy123/p/7003157.html 最近无意间看到一个MySQL分页优化的测试案例,并没有非常具体地说明测试场景的情况下,给出了一种经典的方案,因为现实中很多情况都不是固定不变的,能总结出来通用性的做法或者说是规律,是要考虑非常多的场景的,同时,面对能够达到优化的方式要追究其原因,同样的做法,换了个场景,达不到优化效果的,还要追究其原因.个人对此场景在不用情况表示怀疑,然后自己测试了一把,果然发现一些问题,同时也证实了一些预期的想法.

Java GC 专家系列5:Java应用性能优化的原则

本文是GC专家系列中的第五篇.在第一篇理解Java垃圾回收中我们学习了几种不同的GC算法的处理过程,GC的工作方式,新生代与老年代的区别.所以,你应该已经了解了JDK 7中的5种GC类型,以及每种GC对性能的影响. 在第二篇Java垃圾回收的监控中介绍了在真实场景中JVM是如何运行GC,如何监控GC数据以及有哪些工具可用来方便进行GC监控. 在第三篇GC 调优中基于真实案例介绍了可用于GC调优的最佳选项.同时也描述了如何通过降低移动到老年代中对象的数量来缩短Full GC耗时,以及如何设置GC类

35 个 Java 代码性能优化总结

代码优化的目标是: 1.减小代码的体积 2.提高代码运行的效率 代码优化细节 1.尽量指定类.方法的final修饰符 带有final修饰符的类是不可派生的.在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是final的.为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写.如果指定了一个类为final,则该类所有的方法都是final的.Java编译器会寻找机会内联所有的final方法,内联对于提升Java

Java程序性能优化——性能调优层次

为了提升系统性能,开发人员可以从系统的各个角度和层次对系统进行优化.除了最常见的代码优化外,在软件架构上.JVM虚拟机层.数据库以及操作系统层都可以通过各种手段进行调优,从而在整体上提升系统的性能. 设计调优 设计调优处于所有调优手段的上层,它往往需要在软件开发之前进行.在软件开发之初,软件架构师就应该评估系统可能存在的各种潜在的问题,并给出合理的设计方案.由于软件设计和架构对软件整体有决定性的影响,所以,设计调优对系统性能的影响也是最大的.如果说,代码优化.JVM优化都是对系统微观层面上"量&