使用mongo-spark和spark-redshift连接器

时间:2017-03-21 14:25:58

标签: java mongodb apache-spark apache-spark-sql amazon-redshift

我们正在使用Apache-spark和mongo-spark library(用于连接MongoDB)和spark-redshift library(用于连接Amazon Redshift DWH)。 我们的工作表现非常糟糕。

所以我希望得到一些帮助,以了解我们是否在我们的计划中做了任何错误,或者这是我们对所使用的基础设施的期望。

我们正在4个AWS EC2节点上使用MESOS资源管理器运行我们的工作,并对每个节点进行以下配置:

RAM: 16GB, CPU cores: 4, SSD: 200GB

我们在Redshift集群中有3个表:

TABLE_NAME  SCHEMA                                    NUMBER_OF_ROWS
table1      (table1Id, table2FkId, table3FkId, ...)    50M
table2      (table2Id, phonenumber, email,...)         700M
table3      (table3Id, ...)                            2K

并且在MongoDB中我们有一个包含3500万个文档的集合,下面是一个示例文档(所有电子邮件和电话号码在这里都是唯一的,没有重复):

{
  "_id": "19ac0487-a75f-49d9-928e-c300e0ac7c7c",
  "idKeys": {
    "email": [
      "a@gmail.com",
      "b@gmail.com"
    ],
    "phonenumber": [
      "1111111111",
      "2222222222"
    ]
  },
  "flag": false,
  ...
  ...
  ...
}

我们正在使用spark-mongo连接器过滤和展平(参见mongo-spark聚合管道末尾的代码)以下格式(因为我们需要从Redshift和Mongo ON JOIN数据加入电子邮件或phonenumber匹配另一个可用的选项是spark SQL中的array_contains(),它有点慢):

  {"_id": "19ac0487-a75f-49d9-928e-c300e0ac7c7c", "email": "a@gmail.com", "phonenumber": null},
  {"_id": "19ac0487-a75f-49d9-928e-c300e0ac7c7c","email": "b@gmail.com","phonenumber": null},
  {"_id": "19ac0487-a75f-49d9-928e-c300e0ac7c7c","email": null,"phonenumber": "1111111111"},
  {"_id": "19ac0487-a75f-49d9-928e-c300e0ac7c7c","email": null,"phonenumber": "22222222222"}

Spark计算步骤(请参阅下面的代码以更好地理解这些步骤):

  1. 首先,我们将使用spark-redshift连接器将3个Redshift表中的所有数据分别加载到table1Dataset,table2Dataset,table3Dataset中。
  2. 将这3个表与SparkSQL连接并创建新的数据集redshiftJoinedDataset。 (此操作在6小时内独立完成)
  3. 使用mongo-spark连接器将MongoDB数据加载到mongoDataset。
  4. 加入mongoDataset和redshiftJoinedDataset。 (这是瓶颈,因为我们需要从红移中加入超过5000万行,来自mongodb的超过1亿条扁平行) 注意: - mongo-spark似乎也有一些internal issue with its aggregation pipeline execution可能会让它变得非常慢。
  5. 然后我们正在对finalId进行一些聚合和分组数据
  6. 这是上述步骤的代码:

    import com.mongodb.spark.MongoSpark;
    import com.mongodb.spark.rdd.api.java.JavaMongoRDD;
    import org.apache.spark.SparkContext;
    import org.apache.spark.api.java.JavaSparkContext;
    import org.apache.spark.sql.Dataset;
    import org.apache.spark.sql.SQLContext;
    import org.apache.spark.sql.SparkSession;
    import org.bson.Document;
    
    import java.util.Arrays;
    
    public class SparkMongoRedshiftTest {
    
        private static SparkSession sparkSession;
        private static SparkContext sparkContext;
        private static SQLContext sqlContext;
    
        public static void main(String[] args) {
    
            sparkSession = SparkSession.builder().appName("redshift-spark-test").getOrCreate();
            sparkContext = sparkSession.sparkContext();
            sqlContext = new SQLContext(sparkContext);
    
    
            Dataset table1Dataset = executeRedshiftQuery("(SELECT table1Id,table2FkId,table3FkId FROM table1)");
            table1Dataset.createOrReplaceTempView("table1Dataset");
    
            Dataset table2Dataset = executeRedshiftQuery("(SELECT table2Id,phonenumber,email FROM table2)");
            table2Dataset.createOrReplaceTempView("table2Dataset");
    
            Dataset table3Dataset = executeRedshiftQuery("(SELECT table3Id FROM table3");
            table3Dataset.createOrReplaceTempView("table3Dataset");
    
    
            Dataset redshiftJoinedDataset = sqlContext.sql(" SELECT a.*,b.*,c.*" +
                                                           " FROM table1Dataset a " +
                                                           " LEFT JOIN table2Dataset b ON a.table2FkId = b.table2Id" +
                                                           " LEFT JOIN table3Dataset c ON a.table3FkId = c.table3Id");
            redshiftJoinedDataset.createOrReplaceTempView("redshiftJoinedDataset");
    
            JavaMongoRDD<Document> userIdentityRDD = MongoSpark.load(getJavaSparkContext());
            Dataset mongoDataset = userIdentityRDD.withPipeline(
                    Arrays.asList(
                            Document.parse("{$match: {flag: false}}"),
                            Document.parse("{ $unwind: { path: \"$idKeys.email\"  } }"),
                            Document.parse("{$group: {_id: \"$_id\",emailArr: {$push: {email: \"$idKeys.email\",phonenumber: {$ifNull: [\"$description\", null]}}},\"idKeys\": {$first: \"$idKeys\"}}}"),
                            Document.parse("{$unwind: \"$idKeys.phonenumber\"}"),
                            Document.parse("{$group: {_id: \"$_id\",phoneArr: {$push: {phonenumber: \"$idKeys.phonenumber\",email: {$ifNull: [\"$description\", null]}}},\"emailArr\": {$first: \"$emailArr\"}}}"),
                            Document.parse("{$project: {_id: 1,value: {$setUnion: [\"$emailArr\", \"$phoneArr\"]}}}"),
                            Document.parse("{$unwind: \"$value\"}"),
                            Document.parse("{$project: {email: \"$value.email\",phonenumber: \"$value.phonenumber\"}}")
                    )).toDF();
            mongoDataset.createOrReplaceTempView("mongoDataset");
    
            Dataset joinRedshiftAndMongoDataset = sqlContext.sql(" SELECT a.* , b._id AS finalId " +
                                                                 " FROM redshiftJoinedData AS a INNER JOIN mongoDataset AS b " +
                                                                 " ON b.email = a.email OR b.phonenumber = a.phonenumber");
    
            //aggregating joinRedshiftAndMongoDataset
            //then storing to mysql
        }
    
        private static Dataset executeRedshiftQuery(String query) {
            return sqlContext.read()
                    .format("com.databricks.spark.redshift")
                    .option("url", "jdbc://...")
                    .option("query", query)
                    .option("aws_iam_role", "...")
                    .option("tempdir", "s3a://...")
                    .load();
        }
    
        public static JavaSparkContext getJavaSparkContext() {
            sparkContext.conf().set("spark.mongodb.input.uri", "");
            sparkContext.conf().set("spark.sql.crossJoin.enabled", "true");
            return new JavaSparkContext(sparkContext);
        }
    }
    

    在上述基础设施上完成这项工作的时间估计超过2个月。

    所以要定量地总结这些联接:

    RedshiftDataWithMongoDataJoin => (RedshiftDataJoin)                INNER_JOIN (MongoData)
                                  => (50M LEFT_JOIN 700M LEFT_JOIN 2K) INNER_JOIN (~100M)
                                  => (50M)                             INNER_JOIN (~100M)
    

    对此有任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:2)

因此经过大量调查后,我们发现表2中90%的数据都有电子邮件或电话号码为空,我错过了处理查询中空值的连接。

因此,这是此性能瓶颈的主要问题。

解决此问题后,作业现在可在2小时内运行。

所以spark-redshift或mongo-spark没有问题,表现非常好:)