Spark Streaming保存数据到HBase

本文主要讨论Spark Streaming保存计算结果数据到HBase的实现方案,包括Kerberos认证。

Spark版本:2.11-2.4.0-cdh6.3.2。

HBase版本:2.1.0-cdh6.3.2。

Spark保存数据到HBase,有两种方案:

  • 方案一:使用HBase Client。
  • 方案二:使用Spark API。

每个方案有两种写法,一共四种写法,下面以一个示例进行说明,然后对主要部分进行拆解说明。

完整示例

示例场景:Spark Streaming消费Kafka,计算wordcount,将计算结果保存到HBase。计算结果的key作为rowkey的值,value作为cf:col的值。

示例完整代码:

package com.example.spark;

import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapred.TableOutputFormat;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.spark.SparkConf;
import org.apache.spark.SparkFiles;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaDStream;
import org.apache.spark.streaming.api.java.JavaInputDStream;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.streaming.kafka010.*;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import scala.Tuple2;

import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Consumes messages from one or more topics in Kafka and does wordcount, then save to HBase.
 * Usage: Kafka2Spark2HBase <brokers> <groupId> <topics>
 *   <brokers> is a list of one or more Kafka brokers
 *   <groupId> is a consumer group name to consume from topics
 *   <topics> is a list of one or more kafka topics to consume from
 *
 * Example:
 * spark-submit \
 * 	--master yarn \
 * 	--principal xingweidong \
 * 	--keytab xingweidong.keytab \
 * 	--conf "spark.security.credentials.hbase.enabled=true" \
 * 	--class com.example.spark.Kafka2Spark2HBase \
 * 	devexample-1.0-SNAPSHOT.jar \
 * 	worker01.bigdata.zxxk.com:9092,worker02.bigdata.zxxk.com:9092 \
 * 	spark-streaming-consumer-group \
 * 	spark_streaming
 */

public final class Kafka2Spark2HBase {
    private static final Pattern SPACE = Pattern.compile(" ");

    /**
     * Spark Streaming消费Kafka。
     * @param jssc
     * @param brokers
     * @param groupId
     * @param topics
     * @return
     */
    public static JavaInputDStream<ConsumerRecord<String, String>> directKafka(JavaStreamingContext jssc, String brokers, String groupId, String topics) {
        Set<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
        Map<String, Object> kafkaParams = new HashMap<>();
        kafkaParams.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, brokers);
        kafkaParams.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        kafkaParams.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        kafkaParams.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);

        // Create direct kafka stream with brokers and topics
        JavaInputDStream<ConsumerRecord<String, String>> inputDStream = KafkaUtils.createDirectStream(
                jssc,
                LocationStrategies.PreferConsistent(),
                ConsumerStrategies.Subscribe(topicsSet, kafkaParams));

        return inputDStream;
    }

    /**
     * 创建HBase配置实例,加载core-site.xml, hbase-site.xml等配置文件资源。
     * @param resources
     * @return
     */
    public static Configuration configWithResource(List<String> resources) {
        Configuration config = HBaseConfiguration.create();

        // 添加必要的配置文件 (hbase-site.xml, core-site.xml)
        for (int i = 0; i < resources.size(); i++) {
            config.addResource(new Path(resources.get(i)));
        }

        return config;
    }

    /**
     * 获取HBase Client连接,包括Kerberos认证。
     * @param resources
     * @param krb5Conf
     * @param principal
     * @param keytabFile
     * @return
     * @throws IOException
     */
    public static Connection getHBaseConn(List<String> resources, String krb5Conf, String principal, String keytabFile) throws IOException {
        Configuration config = Kafka2Spark2HBase.configWithResource(resources);

        // 进行Kerberos认证
        // 设置java安全krb5.conf
        System.setProperty("java.security.krb5.conf", krb5Conf);
        // 设置用户主体(Principal)
        config.set("kerberos.principal" , principal);
        // 使用用户keytab文件认证
        config.set("keytab.file" , keytabFile);

        UserGroupInformation.setConfiguration(config);
        try {
            // 登录
            UserGroupInformation.loginUserFromKeytab(principal, keytabFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 创建连接
        return ConnectionFactory.createConnection(config);
    }

    /**
     * 使用HBase Client方式,每条数据连接一次HBase,写入数据。
     * @param rdd
     * @param resources
     * @param krb5Conf
     * @param principal
     * @param keytabFile
     * @param hbaseTableName
     */
    public static void saveToHBase11(JavaPairRDD<String, Integer> rdd, List<String> resources, String krb5Conf, String principal, String keytabFile, String hbaseTableName) {
        rdd.foreach(line -> {
            Connection connection = Kafka2Spark2HBase.getHBaseConn(resources, krb5Conf, principal, keytabFile);
            Table table = connection.getTable(TableName.valueOf(hbaseTableName));

            try {
                Put put = new Put(Bytes.toBytes(line._1));
                put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
                table.put(put);
            } finally {
                table.close();
                connection.close();
            }
        });
    }

    /**
     * 使用HBase Client方式,每个分区连接一次HBase,写入数据。
     * @param rdd
     * @param resources
     * @param krb5Conf
     * @param principal
     * @param keytabFile
     * @param hbaseTableName
     */
    public static void saveToHBase12(JavaPairRDD<String, Integer> rdd, List<String> resources, String krb5Conf, String principal, String keytabFile, String hbaseTableName) {
        rdd.foreachPartition(partitionOfRecords -> {
            Connection connection = Kafka2Spark2HBase.getHBaseConn(resources, krb5Conf, principal, keytabFile);
            Table table = connection.getTable(TableName.valueOf(hbaseTableName));

            try {
                while (partitionOfRecords.hasNext()) {
                    Tuple2<String, Integer> line = partitionOfRecords.next();
                    Put put = new Put(Bytes.toBytes(line._1));
                    put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
                    table.put(put);
                }
            } finally {
                table.close();
                connection.close();
            }
        });
    }

    /**
     * 使用Spark JavaPairRDD saveAsHadoopDataset 方法。
     * @param rdd
     * @param resources
     * @param hbaseTableName
     */
    public static void saveToHBase21(JavaPairRDD<String, Integer> rdd, List<String> resources, String hbaseTableName) {
        Configuration config = Kafka2Spark2HBase.configWithResource(resources);

        JobConf jobConfig = new JobConf(config);
        jobConfig.set(TableOutputFormat.OUTPUT_TABLE, hbaseTableName);
        jobConfig.setOutputFormat(TableOutputFormat.class);

        JavaPairRDD<ImmutableBytesWritable, Put> hbasePuts = rdd.mapToPair(line -> {
            Put put = new Put(Bytes.toBytes(line._1));
            put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
            return new Tuple2<>(new ImmutableBytesWritable(), put);
        });
        hbasePuts.saveAsHadoopDataset(jobConfig);
    }

    /**
     * 使用Spark JavaPairRDD saveAsNewAPIHadoopDataset 方法。
     * @param rdd
     * @param resources
     * @param hbaseTableName
     */
    public static void saveToHBase22(JavaPairRDD<String, Integer> rdd, List<String> resources, String hbaseTableName) {
        Configuration config = Kafka2Spark2HBase.configWithResource(resources);
        config.set("hbase.mapred.outputtable", hbaseTableName);
        config.set("mapreduce.job.outputformat.class", "org.apache.hadoop.hbase.mapreduce.TableOutputFormat");

        JavaPairRDD<ImmutableBytesWritable, Put> hbasePuts = rdd.mapToPair(line -> {
            Put put = new Put(Bytes.toBytes(line._1));
            put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
            return new Tuple2<>(new ImmutableBytesWritable(), put);
        });
        hbasePuts.saveAsNewAPIHadoopDataset(config);
    }

    public static void main(String... args) throws Exception {
        if (args.length < 3) {
            System.err.println("Usage: Kafka2Spark2HBase <brokers> <groupId> <topics>\n" +
                    "  <brokers> is a list of one or more Kafka brokers\n" +
                    "  <groupId> is a consumer group name to consume from topics\n" +
                    "  <topics> is a list of one or more kafka topics to consume from\n\n");
            System.exit(1);
        }

        String brokers = args[0];
        String groupId = args[1];
        String topics = args[2];

        // Create context with a 2 seconds batch interval
        SparkConf sparkConf = new SparkConf().setAppName("Kafka2Spark2HBase");
        JavaStreamingContext jssc = new JavaStreamingContext(sparkConf, Durations.seconds(2));

        List<String> resources = new ArrayList<String>() {
            {
                add("/etc/hbase/conf/core-site.xml");
                add("/etc/hbase/conf/hbase-site.xml");
                add("/etc/hbase/conf/hdfs-site.xml");
            }
        };

        // Kerberos认证需要的配置
        String krb5Conf = "/etc/krb5.conf";
        String principal = "xingweidong@BIGDATA.ZXXK.COM";
        String keytabFile = SparkFiles.get("xingweidong.keytab");  // SparkFiles操作要放到SparkContext之后,否则会报空指针异常
        // HBase表名
        String hbaseTableName = "test:test";

        // 消费kafka
        JavaInputDStream<ConsumerRecord<String, String>> inputDStream = Kafka2Spark2HBase.directKafka(jssc, brokers, groupId, topics);
        JavaDStream<String> dStream = inputDStream.map(ConsumerRecord::value);

        // word count
        JavaDStream<String> words = dStream.flatMap(x -> Arrays.asList(SPACE.split(x)).iterator());
        JavaPairDStream<String, Integer> wordCounts = words.mapToPair(s -> new Tuple2<>(s, 1))
                .reduceByKey((i1, i2) -> i1 + i2);

        wordCounts.print();

        // 去除key为空字符串的数据,否则存入HBase时会报错。
        JavaPairDStream<String, Integer> wordCountsWithNotNull = wordCounts.filter(line -> line._1.length() > 0);

        // 存入HBase
        wordCountsWithNotNull.foreachRDD(rdd -> {
//            Kafka2Spark2HBase.saveToHBase11(rdd, resources, krb5Conf, principal, keytabFile, hbaseTableName);
//            Kafka2Spark2HBase.saveToHBase12(rdd, resources, krb5Conf, principal, keytabFile, hbaseTableName);
//            Kafka2Spark2HBase.saveToHBase21(rdd, resources, hbaseTableName);
            Kafka2Spark2HBase.saveToHBase22(rdd, resources, hbaseTableName);
        });

        // Start the computation
        jssc.start();
        jssc.awaitTermination();
    }
}

上述示例中,需要注意的地方主要包括以下几个部分:

  • HBase Client连接。

  • 任务提交。

  • 主函数。

  • Spark保存数据到HBase方案一。包括saveToHBase11saveToHBase12两种方式。

  • Spark保存数据到HBase方案二。包括saveToHBase21saveToHBase22两种方式。

接下来按照顺序对这几部分进行详细描述。

HBase Client连接

示例代码中有一部分是Java通过HBase Client连接HBase的逻辑,这里不作过多介绍,详情请参考 Java连接HBase(含Kerberos)

任务提交方式说明

本节主要描述与本文主题相关的spark-submit提交任务的注意事项。Spark master为YARN。

这里假设程序的jar包名为devexample-1.0-SNAPSHOT.jar,程序参数值如下:

参数
brokersworker01.bigdata.zxxk.com:9092,worker02.bigdata.zxxk.com:9092
groupIdspark-streaming-consumer-group
topicsspark_streaming

Spark用户和HBase用户一致

提交命令:

spark-submit \
	--master yarn \
	--principal xingweidong \
	--keytab xingweidong.keytab \
	--conf "spark.security.credentials.hbase.enabled=true" \
	--class com.example.spark.Kafka2Spark2HBase \
	devexample-1.0-SNAPSHOT.jar \
	worker01.bigdata.zxxk.com:9092,worker02.bigdata.zxxk.com:9092 \
	spark-streaming-consumer-group \
	spark_streaming

主要选项说明

选项说明
–principal只对YARN有用。用户主体,用于Kerberos认证。
–keytab只对YARN有用。用户keytab,用于Kerberos认证。可以通过SparkFiles.get(fileName) 访问。
–conf “spark.security.credentials.hbase.enabled=true”启用spark连接hbase的安全证书。如果设置为false,则无法连接到启用安全的hbase服务。在CDH中,这个配置默认为true,可以不在spark-submit指定配置。

Spark用户和HBase用户不一致

假设Spark任务用户主体是xingweidong,HBase用户主体是hbase_xingweidong

提交命令:

spark-submit \
	--master yarn \
	--principal xingweidong \
	--keytab xingweidong.keytab \
	--files hbase_xingweidong.keytab#hbaseuser.keytab \
	--conf "spark.security.credentials.hbase.enabled=true" \
	--class com.example.spark.Kafka2Spark2HBase \
	devexample-1.0-SNAPSHOT.jar \
	worker01.bigdata.zxxk.com:9092,worker02.bigdata.zxxk.com:9092 \
	spark-streaming-consumer-group \
	spark_streaming \
	hbaseuser

这里主要增加了--files选项,用于提交额外的资源文件,选项说明如下:

Comma-separated list of files to be placed in the working directory of each executor. File paths of these files in executors can be accessed via SparkFiles.get(fileName).

使用--files选项添加的文件,可以通过SparkFiles.get(fileName)进行访问,另外--files选项支持别名,使用#标记别名,例如hbase_xingweidong.keytab#hbaseuser.keytabhbase_xingweidong.keytab文件的别名是hbaseuser.keytab,如果使用了别名,那么SparkFiles.get(fileName)fileName需要指定为别名。别名功能方便我们在应用中使用固定的文件名获取资源,而不需要依赖实际的文件名。

Main函数主要部分说明

假设Spark用户和HBase用户一致。

SparkFiles说明

代码片段:

String keytabFile = SparkFiles.get("xingweidong.keytab");

这里使用SparkFiles获取keytab文件,用于HBase客户端认证。SparkFiles操作要放到SparkContext之后,否则会报空指针异常。

结果数据处理

代码片段:

// 去除key为空字符串的数据,否则存入HBase时会报错。
JavaPairDStream<String, Integer> wordCountsWithNotNull = wordCounts.filter(line -> line._1.length() > 0);

这里针对HBase的数据要求做了一些处理,在本文的示例,结果数据是(word, count)的<k, v>结构,其中word作为HBase的rowkey存储,因为HBase的rowkey要求是非空字符串,所以需要对结果数据集进行过滤。

在实际的应用中,可能需要更多的处理。

保存到HBase

代码片段:

// 存入HBase
wordCountsWithNotNull.foreachRDD(rdd -> {
    // Kafka2Spark2HBase.saveToHBase11(rdd, resources, krb5Conf, principal, keytabFile, hbaseTableName);
    // Kafka2Spark2HBase.saveToHBase12(rdd, resources, krb5Conf, principal, keytabFile, hbaseTableName);
    // Kafka2Spark2HBase.saveToHBase21(rdd, resources, hbaseTableName);
    Kafka2Spark2HBase.saveToHBase22(rdd, resources, hbaseTableName);
});

这里有两个重点,一个是foreachRDD,另一个是将rdd保存到HBase的方法。

foreachRDD是Spark Streaming通用的输出算子,用于将数据保存到文件、数据库等外部系统,Spark官方介绍如下:

The most generic output operator that applies a function, func, to each RDD generated from the stream. This function should push the data in each RDD to an external system, such as saving the RDD to files, or writing it over the network to a database. Note that the function func is executed in the driver process running the streaming application, and will usually have RDD actions in it that will force the computation of the streaming RDDs.

将rdd保存到HBase的两种方案都是借助foreachRDD完成,接下来的内容就对这两种方案进行重点描述和分析。

Spark保存数据到HBase方案一

该方案是参考自Spark官方文档 Design Patterns for using foreachRDD。这个文档描述了如何使用foreachRDD将数据保存到外部系统的通用方式。

我在此基础上结合了HBase Client连接,将数据保存到HBase。

该方案有两种方法,在示例中分别是:

  • saveToHBase11
  • saveToHBase12 (推荐)

这两种方法都是参考自上面提到的Spark官方文档。性能上,saveToHBase12这种方法会好一些,具体可以分析代码和参考上面提到的Spark官方文档。

saveToHBase11

代码:

/**
 * 使用HBase Client方式,每条数据连接一次HBase,写入数据。
 * @param rdd
 * @param resources
 * @param krb5Conf
 * @param principal
 * @param keytabFile
 * @param hbaseTableName
 */
public static void saveToHBase11(JavaPairRDD<String, Integer> rdd, List<String> resources, String krb5Conf, String principal, String keytabFile, String hbaseTableName) {
    rdd.foreach(line -> {
        Connection connection = Kafka2Spark2HBase.getHBaseConn(resources, krb5Conf, principal, keytabFile);
        Table table = connection.getTable(TableName.valueOf(hbaseTableName));

        try {
            Put put = new Put(Bytes.toBytes(line._1));
            put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
            table.put(put);
        } finally {
            table.close();
            connection.close();
        }
    });
}

这个方法是通过rdd的foreach方法,每一行数据都需要单独创建HBase连接,将数据保存进HBase,然后关闭连接,需要频繁进行HBase连接的创建关闭。

saveToHBase12

代码:

/**
 * 使用HBase Client方式,每个分区连接一次HBase,写入数据。
 * @param rdd
 * @param resources
 * @param krb5Conf
 * @param principal
 * @param keytabFile
 * @param hbaseTableName
 */
public static void saveToHBase12(JavaPairRDD<String, Integer> rdd, List<String> resources, String krb5Conf, String principal, String keytabFile, String hbaseTableName) {
    rdd.foreachPartition(partitionOfRecords -> {
        Connection connection = Kafka2Spark2HBase.getHBaseConn(resources, krb5Conf, principal, keytabFile);
        Table table = connection.getTable(TableName.valueOf(hbaseTableName));

        try {
            while (partitionOfRecords.hasNext()) {
                Tuple2<String, Integer> line = partitionOfRecords.next();
                Put put = new Put(Bytes.toBytes(line._1));
                put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
                table.put(put);
            }
        } finally {
            table.close();
            connection.close();
        }
    });
}

这个方法是通过rdd的foreachPartition方法,每一个分区都需要单独创建HBase连接,将数据保存进HBase,然后关闭连接。

相比于saveToHBase11的方法,理论上,saveToHBase12创建HBase连接的次数会更少。

Spark保存数据到HBase方案二

该方案同样是参考自Spark官方文档 Design Patterns for using foreachRDD

我在此基础上使用了Spark提供的算子,将数据保存到HBase。

该方案有两种方法,在示例中分别是:

  • saveToHBase21
  • saveToHBase22 (推荐)

该方案使用Spark任务的提交用户访问HBase,所以需要确保提交用户拥有必要的HBase相关表的操作权限。

前置知识

在介绍这两种方法之前,需要先补充一下Hadoop MapReduce的一些知识。

Hadoop MapReduce分为两套API,分别是:

  • mapred
  • mapreduce

其中mapreduce相对于mapred较新,一般称为新API。

在Hadoop生态中,大部分服务都对接了这两套API,比如本文涉及到的HBase和Spark。

在HBase中,对应关系如下:

MapReduce APIHBase API
mapredorg.apache.hadoop.hbase.mapred.*
mapreduceorg.apache.hadoop.hbase.mapreduce.*

在Spark中,本文用到的两个算子的对应关系:

MapReduce APISpark API
mapredsaveAsHadoopDataset
mapreducesaveAsNewAPIHadoopDataset

其中saveToHBase21方式使用Spark的saveAsHadoopDataset算子,saveToHBase22方式使用Spark的saveAsNewAPIHadoopDataset算子。

在服务的衔接上,需要使用配套的API。

saveToHBase21

代码:

/**
 * 使用Spark JavaPairRDD saveAsHadoopDataset 方法。
 * @param rdd
 * @param resources
 * @param hbaseTableName
 */
public static void saveToHBase21(JavaPairRDD<String, Integer> rdd, List<String> resources, String hbaseTableName) {
    Configuration config = Kafka2Spark2HBase.configWithResource(resources);

    JobConf jobConfig = new JobConf(config);
    jobConfig.set(TableOutputFormat.OUTPUT_TABLE, hbaseTableName);
    jobConfig.setOutputFormat(TableOutputFormat.class);

    JavaPairRDD<ImmutableBytesWritable, Put> hbasePuts = rdd.mapToPair(line -> {
        Put put = new Put(Bytes.toBytes(line._1));
        put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
        return new Tuple2<>(new ImmutableBytesWritable(), put);
    });
    hbasePuts.saveAsHadoopDataset(jobConfig);
}

该方法使用Spark的saveAsHadoopDataset算子,这个算子可以将RDD输出到任何Hadoop支持的系统。

应用注意事项:

  1. saveAsHadoopDataset参数类型是JobConf,所以需要将配置转换成JobConf。
  2. JobConf需要设置OutputFormat,包括输出表名和输出格式。

其中TableOutputFormat的包路径是org.apache.hadoop.hbase.mapred.TableOutputFormat

saveToHBase22

代码:

/**
 * 使用Spark JavaPairRDD saveAsNewAPIHadoopDataset 方法。
 * @param rdd
 * @param resources
 * @param hbaseTableName
 */
public static void saveToHBase22(JavaPairRDD<String, Integer> rdd, List<String> resources, String hbaseTableName) {
    Configuration config = Kafka2Spark2HBase.configWithResource(resources);
    config.set("hbase.mapred.outputtable", hbaseTableName);
    config.set("mapreduce.job.outputformat.class", "org.apache.hadoop.hbase.mapreduce.TableOutputFormat");

    JavaPairRDD<ImmutableBytesWritable, Put> hbasePuts = rdd.mapToPair(line -> {
        Put put = new Put(Bytes.toBytes(line._1));
        put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes(line._2.toString()));
        return new Tuple2<>(new ImmutableBytesWritable(), put);
    });
    hbasePuts.saveAsNewAPIHadoopDataset(config);
}

该方法使用Spark的saveAsNewAPIHadoopDataset算子,这个算子可以将RDD输出到任何Hadoop支持的系统。

应用注意事项:

  1. saveAsNewAPIHadoopDataset参数类型是Configuration,可以直接使用目标系统的配置。
  2. Configuration需要设置OutputFormat,包括输出表名和输出格式。

这里的OutputFormat使用的是org.apache.hadoop.hbase.mapred.TableOutputFormat

拓展

关于saveAsNewAPIHadoopDataset和saveAsHadoopDataset的更多内容可以参考:

saveAsHadoopDataset和saveAsNewAPIHadoopDataset源码分析及用法说明

结束语

在本文描述的两个方案中,其中方案一比较通用,可以应多大部分情况,在处理连接池可以重用的外部系统时,可以进一步提高性能;方案二只适用于Hadoop支持的系统,使用比较方便。

每个方案都有一个推荐的方法,在使用时可以优先选择。

这里没有做具体的性能对比,不过针对于本文的示例场景,我觉得使用saveAsNewAPIHadoopDataset算子可能是更好的选择。

在实际使用中,需要分析具体情况,来选择较合适的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值