大数据框架调研-批处理-Hadoop与Spark对比

实验结果

离线处理(批处理)

对比两大数据离线处理框架Hadoop和Spark。

结论

Spark相比Hadoop拥有更高的执行效率,能够更快的完成任务的执行;

Spark在复杂任务的处理可以在一个任务中完成,而Hadoop则需要将复杂任务拆分成多个MR去串联执行;

Spark相比Hadoop提供了更加丰富的数据输入和输出的方式,很多输入输出方式都能够直接使用而不用去自定义;

Spark虽然是由scala编写的,但是scala本身与java语言非常相似,本身也能够调用几乎所有的java类库,因此学习成本不高;

离线数据处理,在数据量不是特别大的情况下推荐使用Spark,因为Spark是基于内存计算的,因此对于特大数据处理MapReduce还是有无法替代的优势。

如果数据源不是文件的话,Spark也可以不依赖Hadoop独立部署。

使用Spark作为批处理框架与onos进行联动时,小程序的存在是很有必要的,可以通过小程序的Spark-launcher对Spark任务进行提交和监控,方便任务的管理。

数据源(大量数据)

Hadoop:HDFS文件,自定义数据输入

Spark:HDFS文件,自定义数据输入,但是框架有封装了一些常见的数据源如数据库、csv、json等结构化文件

计算引擎

Hadoop:

MapReduce,通过java编写计算代码,将计算结果进行输出

Hive,通过编写类SQL语句进行任务的生成,可以通过select语句直接将计算结果获取到或是进行存储,但是只能对结构化数据(可以通过MapReduce先进行数据清洗)进行处理

MapReduce多用于数据清洗,然后将清洗完成的干净的结构化数据导入到Hive表中,通过Hive-SQL进行数据的统计和获取。

Spark:

Spark-core,通过Java或者scala编写计算代码,将计算结果进行输出

Spark-SQL,通过Java或者scala编写DDL代码或者编写类SQL语句进行任务的生成,可以直接计算结果获取到或是进行存储,但是只能对结构化数据(可以通过MapReduce先进行数据清洗)进行处理

数据输出

Hadoop:

MapReduce默认以文件方式输出,能够自定义数据的输出,但是比较复杂

Hive可以直接通过类SQL语句创建计算任务并获取到计算结果,也可以将计算结果保存到文件中,无法自定义其他的数据输出

Spark:

Spark-core可以选择输出到文件或者自定义数据输出,通过转换成DataFrame数据结构可以很方便的将数据输出到数据库或是其他结构化文件中

Spark-SQL可以直接通过类SQL语句或者DDL代码,创建计算任务并获取到计算结果,也可以很方便的将数据输出到数据库或是其他结构化文件中

执行效率

Hadoop:基于磁盘进行计算,任务执行过程中会有多次读写磁盘的操作(任务之间的通信),效率相对低下

Spark:基于内存进行计算,任务执行过程中只有Shuffle操作会涉及到读写磁盘的操作(任务之间的通信),效率高

任务提交

Hadoop

MapReduce可以通过jar包+Shell命令、ssh命令或者是第三方任务调度组件azkaban、Oozie等方法

jar包提交的方法依赖大数据集群的小程序,但是可以通过小程序来进行任务的管理和拓展;

ssh命令直接提交方法不依赖小程序,但是对于任务的管理更加不方便;

使用第三方组件组件能够更好的处理复杂任务单元之间的前后依赖关系。

Hive则能够直接在onos中通过JDBC连接Hive,然后执行Hive的SQL语句,可以不依赖小程序或是第三方组件。

Spark

Spark的提交方法jar包+Shell命令、jar包+Spark-launcher、ssh命令

jar包+Shell命令、jar包+Spark-launcher方法均需要依赖小程序,后者通过launcher能够更好的对Spark任务进行管理;

ssh命令直接提交方法不依赖小程序,但是对于任务的管理更加不方便;

Spark 提供了以Spark-launcher 作为JAVA API编程的方式提交,这种方式不需要使用命令行,能够实现与Spring整合,让应用在tomcat中运行;Spark-launcher值支持将任务运行,日志分开输出,便于问题的回溯,可以自定义监听器,当信息或者状态变更时,进行相关操作,支持暂停、停止、断连、获得AppId、获得任务State等多种功能。

离线处理

测试环境

Hadoop的HDFS作为源数据保存,以及部分测试的结果数据保存;

Hadoop的Yarn作为任务运行的资源调度管理器。

数据获取与采集

本次调研测试将数据文件直接上传到HDFS上(/bigdata/server/batch/),数据文件如下:

wei

hello caocao
ni hao caocao
ni shi caocao ma
ni zhen shi caocao
hello word
hello wei

shu

hello liubei
ni hao liubei
ni shi liubei ma
ni zhen shi liubei
hello word
hello shu

wu

hello sunquan
ni hao sunquan
ni shi sunquan ma
ni zhen shi sunquan
hello word
hello wu

离线数据可以通过以下方式进行采集:

1、可以通过Flume监控日志文件的方式,然后传输的HDFS,但是要将有特定的日志格式或者写到特定的日志当中;;

2、通过Kafka消息队列+Flume将数据写入到HDFS当中;

3、将数据库中的数据通过sqoop组件将其导入到hive(HDFS)中;

job提交与运行

Hadoop

MapReduce

MR任务代码

public class WordCountDriver {

    public static void main (String[] args) throws Exception {

        // 1、获取Job对象
        Configuration conf = new Configuration();

        Job job = Job.getInstance(conf);

        // 2、设置jar文件存储位置,即驱动类的路径
        job.setJarByClass(WordCountDriver.class);

        // 3、关联Map和Reduce类
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);

        // 4、设置Mapper阶段输出数据的key和value类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 5、设置最终数据输出的key和value
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 6、设置程序输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        // 7、提交Job对象
//        job.submit();
        job.waitForCompletion(true);
    }
}


public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

    Text k = new Text();
    IntWritable v = new IntWritable(1);

    @Override
    protected void map (LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        // hello hello hello
        System.out.println(key.toString() + ", " + value.toString());

        // 1、获取一行数据
        String line = value.toString();

        // 2、切割单词
        String[] words = line.split(" ");

        // 3、循环写出
        for (String word : words) {
            k.set(word);
            context.write(k, v);
        }
    }
}

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {

    IntWritable v = new IntWritable();

    @Override
    protected void reduce (Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        // cao,1
        // cao,1
        // 会按照String的顺序进行排序

        // 1、累加求和
        int sum = 0;
        for (IntWritable value : values) {
            sum += value.get();
        }

        // 2、写出
        v.set(sum);
        context.write(key, v);
    }
}
public class HadoopMapReduceServiceImpl implements HadoopMapReduceService {

    @Autowired
    TaskService taskService;

    private final Logger log = LoggerFactory.getLogger(TaskServiceImpl.class);

    private FileSystem hdfs;

    @PostConstruct
    public void init() {

        log.info("init hadoop env");

        // 初始化hdfs客户端对象
        try {
            Configuration conf = new Configuration();
            hdfs = FileSystem.get(new URI("hdfs://10.10.10.113:8020"), conf, "bd");
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new BusinessException("初始化Hadoop环境失败");
        }
    }

    @PreDestroy
    public void destroy() throws IOException {

        log.info("destroy hadoop env");

        hdfs.close();
    }

    @Override
    public void execHadoopMapReduce(BatchTaskCreateCondition condition, File jarFile) throws Exception {

        // 判断HDFS中输出文件夹是否存在,而存在则将去删除
        delHdfsFolder(condition.getDataOut());

        String command = condition.getCommand().replace(condition.getJarName(), jarFile.getPath());

        command += getCommandLogOutSuf(jarFile);

        // 执行任务
        new Thread(() -> {
            Process process = null;
            try {
                process = FileUtil.cmdCommandExecute(command);
                int status = process.waitFor();
                log.info("{} result status is {}", command, status);
                process.destroy();
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }).start();
    }

    /**
     * 删除HDFS文件夹
     * @param path
     * @throws Exception
     */
    private void delHdfsFolder(String path) throws Exception {

        Path folderPath = new Path(path);

        if (hdfs.exists(folderPath)) {
            log.info("delete hdfs folder path is {}", path);
            hdfs.delete(folderPath, true);
        }
    }
}

如果不通过小程序的话可以通过ssh的命令直接去运行这个jar包也可以做到。

hadoop jar HadoopWordCount.jar com.starnet.server.bigdata.hadoop.wordcount.WordCountDriver /bigdata/server/batch /bigdata/server/batchOut/HADOOP
结果获取

hadoop的结果可以直接读取结果文件的方式进行获取

或者通过执行hive命令进行查询获取。

读取结果文件的方式获取结果

// TODO:需要考虑怎么拿到文件夹下所有的数据文件且不拿到非数据文件;
// TODO:此处测试将具体的路径传入了
String savePath = taskConfig.getJarSavePath() + "/" + System.currentTimeMillis() + "result";
File file = new File(savePath);
try {
    // 下载文件到本地
    /*
             * delSrc: 默认为false,如果是true的话,将会删除源文件,移动文件实际调用的方法就是false情况的本方法
             * src: 源文件路径
             * dest: 目标文件路径
             * useRowLocalFileSystem: 是否使用本地模式,是则不会产生crc文件
             */
    hdfs.copyToLocalFile(false,
                         new Path(condition.getFolderPath()),
                         new Path(file.getPath()),
                         true);
    // 读取文件内容,并返回
    return FileUtil.getFileContentString(file.getPath());
} catch (Exception e) {
    log.error(e.getMessage(), e);
    throw new BusinessException("获取结果失败");
} finally {
    FileUtil.checkFileIsExistAndDelete(file.getPath());
}

通过执行hive命令进行查询获取

需要提前创建好hive表,可以在以上完成任务之后,创建对应的hive表;

这里直接是手动创建

## 创建hive表
hive -e "create table word_count_result(word string, cnt int) row format delimited fields terminated by '\t';"

## 导入数据
hive -e "load data inpath '/bigdata/server/batchOut/HADOOP' into table default.word_count_result;"

方法一:通过shell脚本获取结果

String lastCommand = String.format("sh %s '%s'", HIVE_SHELL, command);

return FileUtil.getCmdExecuteString(lastCommand);

// 脚本内容如下:
hive -e "$1"

方法二:通过jdbc连接hive进行查询

此方法的话,不需要可以提前执行MR,因为hive本身就是MR实现的,可以通过hive去完成MR的任务并将结果查询得到

前提是开启hiveserver2和metastore两个服务,引入hive-jdbc依赖,因为其依赖中会与spring中的tomcat有冲突所以要排除某些jar

String connectionURL = "jdbc:hive2://hadoop113:10000";
String drivername = "org.apache.hive.jdbc.HiveDriver";
String username = "";
String password = "";
Class.forName(drivername);
try (Connection con = DriverManager.getConnection(connectionURL, username, password);
     Statement stmt = con.createStatement()) {

    String sql = condition.getCommand();

    log.info("hive sql is {}", sql);

    ResultSet res = stmt.executeQuery(sql);

    StringBuilder sb = new StringBuilder();

    while (res.next()) {
        sb.append(res.getString(1))
            .append(",")
            .append(res.getString(2))
            .append("\n");
    }

    return sb.toString();
} catch (Exception e) {
    log.error(e.getMessage());
    throw new BusinessException("获取hive数据失败");
}

Hive

连接方式与以上hive获取完全一致。

## 创建表
hive -e "create table word_count_query(word string);"

## 导入数据
hive -e "load data inpath '/bigdata/server/batch' into table default.word_count_query;"

查询语句如下:

select w, count(*) as num 
from 
(
    select explode(split(word, ' ')) w from default.word_count_query
)t1 
group by w;

Spark

spark-core方式实现
object WordCount3 {

    def main(args: Array[String]): Unit = {

        val sparkConf = new SparkConf().setMaster("yarn").setAppName("WordCount")
        val sc = new SparkContext(sparkConf)

        val lines = sc.textFile("hdfs://hadoop113:8020/bigdata/server/batch")
        val words = lines.flatMap(_.split(" "))
        val wordToOne = words.map(word => (word, 1))
        val wordCount = wordToOne.reduceByKey(_ + _)

        wordCount.saveAsTextFile("hdfs://hadoop113:8020/bigdata/server/batchOutBySpark")

        sc.stop()
    }
}

那么可以和Hadoop的MapReduce的方法完全一样,将以上代码达成jar包,然后提交运行即可。

/opt/module/spark-yarn/bin/spark-submit --class com.starnet.server.bigdata.spark.wordcount.WordCount --master yarn  --deploy-mode cluster /home/bd/SPARK/spark-test-1.0.0.jar 
通过spark-launcher提交
public void crateBatchTaskByLauncher() throws Exception {

    SparkApplicationParam sparkAppParams = new SparkApplicationParam();
    sparkAppParams.setJarPath("/home/bd/SPARK/spark-test-1.0.0.jar");
    sparkAppParams.setMainClass("com.starnet.server.bigdata.spark.wordcount.WordCount");

    submitApplication(sparkAppParams);
}

public void submitApplication(SparkApplicationParam sparkAppParams, String... otherParams) throws Exception {

    log.info("spark任务传入参数:{}", sparkAppParams.toString());

    Map<String, String> confParams = sparkAppParams.getOtherConfParams();
    SparkLauncher launcher = new SparkLauncher()
        .setSparkHome("/opt/module/spark-yarn")
        .setAppResource(sparkAppParams.getJarPath())
        .setMainClass(sparkAppParams.getMainClass())
        .setMaster(sparkAppParams.getMaster())
        .setDeployMode(sparkAppParams.getDeployMode())
        .setConf("spark.driver.memory", sparkAppParams.getDriverMemory())
        .setConf("spark.executor.memory", sparkAppParams.getExecutorMemory())
        .setConf("spark.executor.cores", sparkAppParams.getExecutorCores());

    if (confParams != null && confParams.size() != 0) {
        log.info("开始设置spark job运行参数:{}", JSONObject.toJSONString(confParams));
        for (Map.Entry<String, String> conf : confParams.entrySet()) {
            log.info("{}:{}", conf.getKey(), conf.getValue());
            launcher.setConf(conf.getKey(), conf.getValue());
        }
    }
    if (otherParams.length != 0) {
        log.info("开始设置spark job参数:{}", otherParams);
        launcher.addAppArgs(otherParams);
    }

    log.info("参数设置完成,开始提交spark任务");

    new Thread(new Runnable() {
        @Override
        public void run() {
            try {

                CountDownLatch countDownLatch = new CountDownLatch(1);

                SparkAppHandle handle = launcher.setVerbose(true).startApplication(new SparkAppHandle.Listener() {

                    @Override
                    public void stateChanged(SparkAppHandle sparkAppHandle) {
                        if (sparkAppHandle.getState().isFinal()) {
                            countDownLatch.countDown();
                        }
                        log.info("stateChanged:{}", sparkAppHandle.getState().toString());
                    }

                    @Override
                    public void infoChanged(SparkAppHandle sparkAppHandle) {
                        log.info("infoChanged:{}", sparkAppHandle.getState().toString());
                    }
                });

                log.info("The task is executing, please wait ....");

                //线程等待任务结束
                countDownLatch.await();

                log.info("The task is finished!");
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }).start();
}


public class SparkApplicationParam {
    /**
     * 任务的主类
     */
    private String mainClass;

    /**
     * jar包路径
     */
    private String jarPath;

    private String master = "yarn";

    private String deployMode = "cluster";

    private String driverMemory = "1g";

    private String executorMemory = "1g";

    private String executorCores = "1";

    /**
     * 其他配置:传递给spark job的参数
     */
    private Map<String, String> otherConfParams;
}

spark-launcher任务提交的日志默认和小程序放到一起,可以通过其他方式将日志单独打印出来,之后要实装Spark的话可以将其日志分开输出,便于问题的回溯,并且可以自定义监听器,当信息或者状态变更时,都能进行操作,支持暂停、停止、断连、获得AppId、获得State等多种功能。

结果获取

1、以上方式是通过saveAsTextFile将结果保存到了HDFS中,那么可以通过直接读取文件或者是hive的方法进行结果数据的读取

2、也可以通过将数据通过 wordCount.collection.foreach(),该方法会将结果一条一条获取出来,可以在foreach方法中将数据通过JDBC或者是Jedis等其他方式写到数据库或redis或者其他保存方法。

wordCount.collect().foreach(data => {
    // JDBC
    // Jedis
    // Kafka
    // ...
    print(data)
})

3、将rdd转换成Spark-SQL的DataFrame或者是DataSet然后保存到数据库

// 涉及到转换操作,需要引入转换规则
val spark: SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()
import spark.implicits._
wordCount.toDF().write.format("jdbc")
    .option("url", "jdbc:mysql://127.0.0.1:3306/test")
    .option("driver", "com.mysql.jdbc.Driver")
    .option("user", "root")
    .option("password", "123456")
    .option("dbtable", "t_table_name")
    .save()
Spark-SQL
object WordCountBySQL {

    def main(args: Array[String]): Unit = {

        // 创建SparkSQL的运行环境
        val sparkConf: SparkConf = new SparkConf().setMaster("yarn").setAppName("SparkSQL")
        val spark: SparkSession = SparkSession.builder().config(sparkConf).getOrCreate()

        // 涉及到转换操作,需要引入转换规则
        import spark.implicits._

        val ds: Dataset[String] = spark.read.textFile("hdfs://hadoop113:8020/bigdata/server/batch")
        val strDs: Dataset[String] = ds.flatMap(_.split(" "))

        // DSL方式实现
        strDs.groupBy("value").count.orderBy('count.desc).show()
        

        // SQL方式
        strDs.createOrReplaceTempView("table_count");

        val sql: String = "select value, count(value) as count_word " +
                    "from table_count " +
                    "group by value " +
                    "order by count_word desc";

        spark.sql(sql).show()

        spark.close()
    }
}

那么可以和Hadoop的MapReduce的方法完全一样,将以上代码达成jar包,然后提交运行即可。

Spark的数据源还可以直接是数据库中的数据,或者是csv、json等其他结构化文件

// mysql
val df: DataFrame = spark.read.format("jdbc")
    .option("url", "jdbc:mysql://127.0.0.1:3306/test")
    .option("driver", "com.mysql.jdbc.Driver")
    .option("user", "root")
    .option("password", "123456")
    .option("dbtable", "t_table_name")
    .load()
结果获取

方式与Spark-core完全一致。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值