MongoDB mapReduce填补自带group的限制问题

用过MongoDB的小伙伴或多或少都会遇到MongoDB自带group的限制问题,即group不支持大于2万的结果集!

我曾在任职公司做过某网站小区均价数据抓取,采用的是Groovy技术。数据抓取下来后存放在MongoDB中。数据抓取完成都是需要经历数据清洗处理的,其中有一项数据去重时候就遇到MongoDB自带group的限制问题,即group不支持大于2万的结果集。几番折腾后来采用MongoDB mapReduce来处理。

下面是数据去重的部分核心代码,已做脱敏处理!

package com.script.thirdPriceCrawl

import com.script.util.MongoUtils
import com.mongodb.BasicDBObject
import com.mongodb.DB
import com.mongodb.DBCollection
import com.mongodb.DBObject
import com.mongodb.MapReduceOutput
import org.apache.commons.lang.StringUtils

import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

/**
 * 网络爬虫:
 * 某网站-小区均价数据抓取<br>
 * http://www.xxx.com/
 * 
 * 解析完成后通过MapReduce统计/清理重复数据工具类<br>
 * 在清洗数据脚本执行之前调用
 *
 * @author 小辉哥/小辉GE
 * 2019年9月5日 下午21:22:12
 *
 */
class ThirdPartPriceDataRepeatBatchUtils {
    //MapReduce 执行分组后输出DBCollection
    def static final THIRD_PARTY_PRICE_DATAREPEAT_TEMP = "third_party_price_repeat_data_temp"
    def static final THIRD_PARTY_PRICE_DATAPRICEREPEAT_TEMP = "third_party_price_repeat_dataprice_temp"
    //线程池
    def static ExecutorService fixedThreadPoolQuery
    //其他变量
    def static final CONNECT_CHAR = "#####"

    /**
     * 每次执行时候初始化线程池, 防止类已加载就要多个线程池中线程占用内存
     * @param type
     * @return
     */
    def static initThreadPool(){
        fixedThreadPoolQuery = Executors.newFixedThreadPool(50)
    }

    /**
     * 全量测试执行时间为277分钟, 预计执行时间为五小时
     * 
     * 返回当月总解析后重复数据
     * 判断分组标准:s_date+source+city+region+name
     * 备注:方法在解析完成清洗开始前执行
     * @param sdate
     * @param thirdPartyColl
     * @return
     */
    def static findThirdPriceDataRepeatBatch(sdate, DBCollection thirdPartyColl){
        try {
            println("findThirdPriceDataRepeatBatch 处理重复数据开始")
            long start = System.currentTimeMillis()
            //query条件, 按sdate筛选
            DBObject query = new BasicDBObject().append("s_date", sdate)
            //mapfun
            String mapfun =
                    "function(){" +
                        "emit({s_date:this.s_date, source:this.source, city:this.city, region:this.region, name:this.name}, 1);" +
                    "};"
            //reducefun
            String reducefun =
                    "function(key, values){" +
                        "return values.length;" +
                    "};"
            //执行MapReduce
            MapReduceOutput mapReduce = thirdPartyColl.mapReduce(mapfun, reducefun, THIRD_PARTY_PRICE_DATAREPEAT_TEMP, query)
            if(mapReduce!=null && mapReduce.results().size()>0){
                //初始化线程池(初始化放在mapReduce.results()结果后, 保证thirdPartyColl.mapReduce发生异常, 线程池未创建)
                initThreadPool()
                mapReduce.results().each { DBObject o ->
                    try{
                        if(o.value > 1) {
                            fixedThreadPoolQuery.execute(
                                new Runnable() {
                                    void run() {
                                        try {
                                            println("调用findThirdPriceDataRepeatByPriceBatch, 传入的obj对象为: "+o.toString())
                                            findThirdPriceDataRepeatByPriceBatch((int)(o._id.s_date), o._id.source, o._id.city, o._id.region, o._id.name, thirdPartyColl)
                                        } catch (Exception e) {
                                            println "调用findThirdPriceDataRepeatByPriceBatch -> DBObject对象为"+o.toString()+"发生异常, 异常信息为:" + e.getMessage()
                                        } finally {

                                        }
                                    }
                                }
                            )
                        }
                    } catch (Exception e) {
                        println("findThirdPriceDataRepeat --> mapReduce.results().each { DBObject o -> DBObject对象为"+o.toString()+", 时发生异常, 异常信息为" + e.getLocalizedMessage())
                    }
                }
                fixedThreadPoolQuery.shutdown()
                fixedThreadPoolQuery.awaitTermination(2, TimeUnit.DAYS)
                long end = System.currentTimeMillis()
                println "findThirdPriceDataRepeatBatch 处理重复数据完成,耗时:" + (end - start)
            }
        } catch (Exception e) {
            println("findThirdPriceDataRepeatBatch(sdate, DBCollection thirdPartyColl){发生异常, 异常信息为" + e.getLocalizedMessage())
        }
    }

    /**
     * 判断分组标准:s_date+source+city+region+name+avg_price
     * @param sdate
     * @param source
     * @param city
     * @param region
     * @param name
     * @param thirdPartyColl
     * @return
     */
    def static findThirdPriceDataRepeatByPriceBatch(sdate, source, city, region, name, DBCollection thirdPartyColl){
        //按价格分组后, 判断是否出现多次不同的价格, 并不是根据分组的count判断,
        //而是根据按照价格分组后, 对应sdate+source+city+region+name 还出现多次在mapReduce.results()中, 即为重复数据
        def dataRepeatMap = [:]
        try{
            //query条件, 按sdate筛选
            DBObject query = new BasicDBObject().append("s_date", sdate)
                    .append("source",source).append("city", city).append("region", region).append("name", name)
            //mapfun
            String mapfun =
                    "function(){" +
                        "emit({s_date:this.s_date, source:this.source, city:this.city, region:this.region, name:this.name, avg_price:this.avg_price}, 1);" +
                    "};"
            //reducefun
            String reducefun =
                    "function(key, values){" +
                        "return values.length;" +
                    "};"
            //执行MapReduce
            MapReduceOutput mapReduce = thirdPartyColl.mapReduce(mapfun, reducefun, THIRD_PARTY_PRICE_DATAPRICEREPEAT_TEMP, query)
            if(mapReduce!=null && mapReduce.results().size()>0){
                mapReduce.results().each { DBObject o ->
                    try{
                        def sd = (int) o._id.s_date
                        def keys = sd + CONNECT_CHAR + o._id.source + CONNECT_CHAR + o._id.city + CONNECT_CHAR + o._id.region + CONNECT_CHAR +  o._id.name
                        if (!dataRepeatMap.containsKey(keys)) {
                            println("dataRepeatMap 中不含有 keys:" + keys + "加入dataRepeatMap")
                            dataRepeatMap.put(keys, keys)
                        } else {
                            println("dataRepeatMap 中含有 keys:" + keys + "执行删除方法, 对象为: " + o.toString())
                            deleteThirdPriceDataBatch((int) (o._id.s_date), o._id.source, o._id.city, o._id.region, o._id.name, thirdPartyColl)
                        }
                    } catch (Exception e) {
                        println("调用deleteThirdPriceDataBatch DBObject对象为"+o.toString()+", 时发生异常, 异常信息为" + e.getLocalizedMessage())
                    }
                }
            }
        }catch (Exception e) {
            println("findThirdPriceDataRepeatByPriceBatch(sdate, source, city, region, name, DBCollection thirdPartyColl){发生异常, 异常信息为" + e.getLocalizedMessage())
        }
    }

    /**
     * 删除重复数据
     * @param sdate
     * @param source
     * @param city
     * @param region
     * @param name
     * @param thirdPartyColl
     */
    def static deleteThirdPriceDataBatch(sdate, source, city, region, name, DBCollection thirdPartyColl){
        DBObject obj = new BasicDBObject();
        obj.put("s_date", sdate)
        obj.put("source", source)
        obj.put("city", city)
        obj.put("region", region)
        obj.put("name", name)
        thirdPartyColl.remove(obj);
    }

}

输出结果在这里就不展示了!!!

以上代码仅供参考,如有不当之处,欢迎指出!!!

更多干货,欢迎大家关注和联系我。期待和大家一起更好的交流、探讨技术!!!

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值