本文
“
基於MongoDB MapReduce的統計分析
”是在開發oecp社區中的遇到的問題如何解決和經驗總結。
前面已經簡單介紹了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));
- query.put("date", newBasicDBObject("$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’}]
前台展現的效果:
繼續關注OECP社區,我們將會實踐和發布更多基於MongoDB的應用。本著共用的精神,該文檔可以被轉載和應用,但是要註明出處。請關注oecp社區 yongtree的部落格