前面已经简单介绍了MongoDB在OECP社区的一个应用:
动态消息的设计实现。在上次的应用中,咱们只介绍了MongoDB最基本的查询的功能,今天我再介绍一下MongoDB更加高级的应用:用MongoDB作统计分析。
OECP社区中,咱们为了更加准确的分析网站的访问状况,以便可以为用户更准确的推荐他们感兴趣的内容,咱们须要将页面的访问记录存储下来。对于这些数据,主要由如下几个特色:
- 与业务无关,尽可能将数据存储和业务数据分离,减小业务数据库的压力。并且对数据的一致性要求不高。
- 每当访问一个页面就要存储一条记录,实时插入操做的要求很高,固然可使用缓存做为临时缓冲来解决数据频繁更新的问题。
- 数据随着访问量的增加膨胀的很快,若是一个页面1天有100个PageViews,将会新增100条数据,数据量远远高于业务数据,并且要比咱们上次说的消息动态的数据的数量级要大得多。网站要尽可能存储至少两个月的数据,当网站访问量很大的时候,要解决的是海量数据的存储。
因此从存储上考虑,咱们依然选择了MongoDB做为持久存储。因为NoSQL数据库在数据查询的多样性能力过低,特别是标准的Key-Value数据库,通常的作法就是用NoSQL负责日志的存储,分析须要将数据抽取到关系数据库中再进行统计查询。可是MongoDB却提供给咱们很是丰富的查询统计功能,group 和MapReduce都能实现SQL中group by,sum,count之类的统计查询分析。Group的功能已经能够实现简单的统计功能,可是当数据量很是大的时候,group处理能力就不太好了,因此咱们一开始就使用MapReduce进行统计分析。
先看一下官方对MapReduce的介绍:
db.runCommand(
{ mapreduce : <collection>,
map : <mapfunction>,
reduce : <reducefunction>
[, query : <query filter object>]
[, sort : <sort the query. useful foroptimization>]
[, limit : <number of objects to returnfrom collection>]
[, out : <output-collection name>]
[, keeptemp: <true|false>]
[, finalize : <finalizefunction>]
[, scope : <object where fields go into javascript global scope >]
[, verbose : true]
}
);
而java驱动下提供的方法主要有两个:
DBCollection.mapReduce(String map, String reduce,String outputCollection,
DBObject query);
DBCollection.mapReduce(DBObject command);//该接口按照上面的介绍,老是报错,不知道此该如何应用
PV数据存储结构:(这些属性主要是为了支持咱们之后根据各类维度去分析)
entityId:实体ID,
entityName:实体名称,
userid:(登陆)访问者ID,
sessionId:会话ID,
referer:来源URL,
url:当前页面
url,
title:显示的标题,
date:访问时间,
ip:访问者IP
第一个应用场景:当访问某用户的空间时,获得某用户最新的访问记录,同一个页面重复访问的话,返回最新的一次访问。
- 首先是map方法,主要是定义outputCollection的结构。OutputCollection的输出结构为:{_id:key,value:value}
java 代码
- String mapfun = "function(){emit({url:this.url,title:this.title},this.date)}";//key={url:this.url,title:this.title},value=reduce方法的返回值。
java 代码
- String reducefun = "function(key,vals){var date=0; for(var i in vals){ if(date==0){date=vals[i];}else if(vals[i]>date){date=vals[i];}} return date;}";//若是同一个key的数据,相互比较时间,将最近时间返回。
java 代码
- DBObject query = newBasicDBObject();
- query.put("userid", userid);
- query.put("date", newBasicDBObject("$gte", fromDate));
- *.getCollection().mapReduce(mapfun, reducefun,"pageview_results", query);//最好定义query,以下降统计的原始结果集
- 遍历pageview_results集合的结果:[{_id:{url:”/blog/yongtree/258”,title:’博客1’},value:’2010-10-11 20:30:56’},{_id:{url:”/blog/slx/288”,title:’博客2’}, value:’2010-10-01 02:23:33’}]
注意:mapfun和reducefun字符串里面是写的javascript的方法,MongoDB能够在服务器端进行js的解析。若是这个方法写的不对,程序将不能正常执行。
第二个应用场景:当访问某个具体的内容时,返回某段时间曾经浏览过这篇文章的其余人关注的其余内容,以便对当前用户有一个内容的引导。
- 首先先找出某段时间内曾经访问该内容的人做为统计的条件,咱们使用sessionId而不是userid,是为了将没有登陆的用户的访问算进来一块儿统计
java 代码
- DBObject query = newBasicDBObject();
- query.put("entityId", entityId);
- query.put("entityName", entityName);
- query.put("date", newBasicDBObject("$gte", fromDate).append("$lt", toDate));
-
- List sessionIds = this.mongoService.getCollection().distinct("sessionId", query);//这里运用了取出结果集中的重复值的函数distinct(String key,DBObject query),至关于SQL:select distinct(name) from table
- 定义map方法,主要是定义outputCollection的结构。OutputCollection的输出结构为:{_id:key,value:次数浏览的次数}
java 代码
- String mapfun = " function(){emit({url:this.url,title:this.title},1)}";//key={url:this.url,title:this.title},value=reduce方法的返回值。觉得是计算数据的次数,因此这里的value定义的是常量1
java 代码
- String reducefun = " function(key,vals){var count=0; for(var i in vals){count+=vals[i];} return count;}";//若是同一个key的数据出现的次数进行求和。
java 代码
- *.getCollection().mapReduce(mapfun, reducefun,"pageview_results", new BasicDBObject("sessionId",new BasicDBObject("$in",sessionIds.toArray())));
- 遍历pageview_results集合的结果:[{_id:{url:”/blog/yongtree/258”,title:’博客1’},value:’45.0’},{_id:{url:”/blog/slx/288”,title:’博客2’}, value:’30.0’}]
前台展示的效果: