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

268 阅读3分钟

一、传统的较为简单的SparkSql方式读取

Maven仓库
org.mongodb.spark
mongo-spark-connector_2.11
2.4.1

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.xxxxx.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 javaRdd = sparkSession.sql(sql).javaRDD().mapPartitions(iterator -> { List 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 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");

  1. 分区实现及限制 对于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)) ArrayMongoPartition

} }

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.xxxx.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. 使用上述方式则不用进行特殊处理