Spark读取MongoDB数据的方法与优化

一、传统的较为简单的SparkSql方式读取
Spark版本:2.4 CDH
MongoDB Spark Connector github地址:​​
​ ​https://github.com/mongodb/mongo-spark​​

Maven仓库
<dependency>    
  <groupId>org.mongodb.spark</groupId>   
  <artifactId>mongo-spark-connector_2.11</artifactId>    
  <version>2.4.1</version>
</dependency>


1.Java API
// 构建数据结构
// 根据实际的业务结构调整
// 建议提前组装好结构
        StructType arrObjectStruct = new StructType()
                .add("xxxx", DataTypes.StringType)
                .add("yyyy", DataTypes.StringType)
                .add("zzzz", DataTypes.IntegerType)
                ;
        StructType sourceSchema = new StructType()
                .add("_class", DataTypes.StringType)
                .add("_id", DataTypes.StringType)
                .add("aaa", DataTypes.LongType)
                .add("bbb", DataTypes.StringType)
                .add("ccc", DataTypes.LongType)
                .add("ddd", DataTypes.createArrayType(arrObjectStruct, true))
                .add("eee", DataTypes.LongType)
                .add("fff", DataTypes.LongType)
                .add("ggg", DataTypes.LongType);

sparkSession.read().schema(sourceSchema)
                    .format("com.mongodb.spark.sql")
                     // MongoDB集合的JDBC链接
                    .option("spark.mongodb.input.uri", "mongodb://www.baidu.com:8080/test/xxxxxx")
                    // 优化选项每批次读取的记录数,默认1000
                    .option("spark.mongodb.input.batchSize", 1000)
                    // 这里定义的是分片实现,后面会介绍
                    .option("spark.mongodb.input.partitioner", "MongoPaginateByCountPartitioner")
                    // 默认参数,分片的主键
                    .option("spark.mongodb.input.partitionerOptions.partitionKey", "_id")
                    // 默认参数,分片数量。 最后生成的executor数量
                    .option("spark.mongodb.input.partitionerOptions.numberOfPartitions", 32)
                    .load().createTempView("tmp_sxxx_xxxx");
// 这时已经生成了临时表
// 接下来就可以像写普通SQL一样处理数据了。大数据培训
// 默认是开启 push down filter 优化的。同时开启null值过滤优化
// 因此进行SQL的增量查询时,where条件会命中索引

// 如果记录中有list需要一条变多条则需要使用mapPartitions算子进行处理
JavaRDD<Row> javaRdd = sparkSession.sql(sql).javaRDD().mapPartitions(iterator -> {
                    List<Row> result = Lists.newArrayList();
                    while (iterator.hasNext()) {
                        Row row = iterator.next();
                        Long a = row.get(0) == null ? -99L : row.getLong(0);
                        Long b = row.get(1) == null ? -99L : row.getLong(1);
                        Long c = row.get(3) == null ? -99L : row.getLong(3);
                        Long d = row.get(4) == null ? -99L : row.getLong(4);
                        Long e = row.get(5) == null ? -99L : row.getLong(5);
                        Seq<Row> xx = row.get(2) == null ? null : row.getAs(2);
                        if (null != lectureRecords) {
                            for (Row x : scala.collection.JavaConversions.seqAsJavaList(xx)) {
                                String f = x.get(0) == null ? "" : x.getString(0);
                                String g = x.get(1) == null ? "" : x.getString(1);
                                Integer h = x.get(2) == null ? -99 : x.getInt(2);
                                    result.add(RowFactory
                                            .create(a,b,c,d,e,f,g,h));
                            }
                        }
                    }
                    return result.listIterator();
                });
sparkSession.createDataFrame(javaRdd, getSchema())
  .write().format("hive").mode(SaveMode.Overwrite).saveAsTable("test.test_mo");

2. 分区实现及限制
对于MongoDB版本的小于3.2的需要显示的指定
如下三个参数
* Setting a "spark.mongodb.input.partitioner" in SparkConf.
* Setting in the "partitioner" parameter in ReadConfig.
* Passing the "partitioner" option to the DataFrameReader.

几种分区的实现:
1. MongoShardedPartitioner
2. MongoSplitVectorPartitioner
3. MongoPaginateByCountPartitioner
4. MongoPaginateBySizePartitioner

具体的解释可以查看。com.mongodb.spark.rdd.partitioner.DefaultMongoPartitioner 原码中的注释
需要根据MongoDB的集群部署方式选择最适合自己的
每种分区实现需要的参数不尽相同。可以在对应的实现内查看


二、自定义分区实现
Spark读取MongoDB数据分区主要是通过继承 MongoPartitioner 特质来实现partitions方法
ScalaAPI实现
1. 实现MongoPartitioner特质
// 可以自定义需要接受的参数,进行特定的处理
class MongoPartitionTest1 extends MongoPartitioner {
  private implicit object BsonValueOrdering extends BsonValueOrdering
  private val DefaultPartitionKey = "_id"
  private val DefaultNumberOfPartitions = "64"

  override def partitions(connector: MongoConnector, readConfig: ReadConfig, pipeline: Array[BsonDocument]): Array[MongoPartition] = {
    // 自定义分区实现,可以参考源码中其他的分区实现
    val boundaryQuery = PartitionerHelper.createBoundaryQuery("_id", new BsonObjectId(new ObjectId("5fedf5800000000000000000")), new BsonObjectId(new ObjectId("5fee03900000000000000000")))
    val mongoPartition = new MongoPartition(0, boundaryQuery, PartitionerHelper.locations(connector))
    Array[MongoPartition](mongoPartition)

  }
}


case object MongoPartitionTest1 extends MongoPartitionTest1

2.读取内容
val mongoReadConfig = new ReadConfig(
      databaseName = "库名",
      collectionName = "集合名",
      connectionString = None,
     // 采样大小
      sampleSize = 1000,
        // 自定义的分区实现
      partitioner = MongoPartitionTest1,
     //  自定义实现接收的参数
      partitionerOptions = Map(),
    //   partitionerOptions = Map("spark.mongodb.input.partitionerOptions.partitionKey" -> "_id", "spark.mongodb.input.partitionerOptions.numberOfPartitions" -> "32"),
     // 本地阀值  
    localThreshold = 15,
    readPreferenceConfig = new ReadPreferenceConfig(),
    readConcernConfig = new ReadConcernConfig(),
    aggregationConfig = AggregationConfig(),
    registerSQLHelperFunctions = false,
    inferSchemaMapTypesEnabled = true,
    inferSchemaMapTypesMinimumKeys = 250,
    // null值过滤
    pipelineIncludeNullFilters = true,
    // push down filter
    pipelineIncludeFiltersAndProjections = true,
    // 样本池大小
    samplePoolSize = 1000,
    //批次数量
    batchSize = Option(1000)
    )

    val mongoConnector = MongoConnector(Map(
        "spark.mongodb.input.uri" -> "mongodb://www.baidu.com:8080",
        "spark.mongodb.input.localThreshold" -> "15"))

    val mongoSpark = MongoSpark.builder()
      .sparkSession(sparkSession)
      .readConfig(mongoReadConfig)
      .connector(mongoConnector)
     .build()
   mongoSpark.toDF().schema(schema).where("everyDate = '2021-01-01' ").show(100, false)

3.注意事项
1.如果使用SparkSql的方式,是使用反射的方式实现的。需要将自己实现的类以Jar包的方式上传到Spark的classPath下。
2. 使用上述方式则不用进行特殊处理

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值