ITPub博客

首页 > 数据库 > 数据库开发技术 > Spark structured streaming写hive动态分区表

Spark structured streaming写hive动态分区表

原创 数据库开发技术 作者:安瑞哥是码农 时间:2022-08-19 11:30:29 0 删除 编辑

在这个发起的实战项目中,有个模块是将数据通过流的方式实时写入到hive的动态分区表中,以实现后续spark batch对其进行的离线批处理分析。

上次直播的时候,好像有小伙伴对Spark如何实时写hive动态分区表比较感兴趣,那么这篇文章咱们就来聊聊这个话题。

其实用spark写hive的动态分区表,在我之前经历过的项目里早就做过了,只不过当时用的是spark streaming,而且当时用的spark版本为spark2.3。

就是这样一个破组合,当时可算是把我给坑惨了,因为官方说,spark2.x不能直接操作hive3.x,原因在于他们无法共享catalog,Emm... 好吧。


好在Hortonworks官方给了一个HWC的救急方案,虽然比较麻烦,但是最终还是能够解决问题,详情可以参考我之前的文章(spark2 + hive3遇到的坑)。

因为spark2 + hive3坑实在太多,而且网上关于他们解决方案的资料也凤毛麟角,为了提高开发效率,我干脆把spark2升级到了spark3,因为官网说spark3解决了与hive3的兼容性问题,详情也可参考之前的文章(Spark2无损升级Spark3).


那么这篇文章就来说说,如何通过spark3的structured streaming动态写hive3的分区表,看看还有哪些坑等着咱...


|先确认集群环境
本案例集群采用HDP3.1(对应Hadoop3.1版本)部署,其各个技术组件的版本如下:

1,kafka:2.0.0版本,HDP3.1自带;
2,spark:3.2.0版本,由原来HDP中的2.3版本升级而来;
3,hive:3.0.0版本,HDP3.1自带;


不是这个版本组合的同学可以自行测试一下其兼容性,或者你的集群中如果刚好是spark2+hive3,那么强烈建议你将spark升级到3.x版本。当然,如果说你想去趟一趟这里面的坑,那就忽略我的建议。


|数据处理流程
该案例通过structured streaming实时读取kafka集群的数据(之前为该项目提供的),然后将数据做简单的清洗、结构化,再根据日期写入hive的动态分区表中。

其处理流程图如下所示:


整个逻辑其实很简单,但是想要把整个流程流畅的跑通,可能没有想象中那么容易。

来,上项目代码:
package com.anryg.bigdata.streaming

import java.util.concurrent.TimeUnit

import com.alibaba.fastjson.JSON
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.streaming.{OutputMode, Trigger}

/**
  * @DESC:  从kafka读取上网数据,写入hive动态分区表
  * @Auther: Anryg
  * @Date: 2022/08/17 12:56
  */

object StructuredStreamingFromKafka2Hive {

    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
                .setAppName("StructuredStreamingFromKafka2Hive")
                .setMaster("local[*]")//本地运行模式,如果提交集群,注释掉这行
        val spark = SparkSession.builder().config(conf)
                .config("spark.sql.hive.hiveserver2.jdbc.url","jdbc:hive2://xxxxxx/;serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2")
                .config("spark.datasource.hive.warehouse.metastoreUri","thrift://xxxxxxx:端口")
                .enableHiveSupport() //打开hive支持功能,可以与hive共享catalog
                .getOrCreate()

         val rawDF = spark.readStream
            .format("kafka") //确定数据源的来源格式
            .option("kafka.bootstrap.servers", "xxxxxx:端口") //指定kafka集群的地址,理论上写一个broker就可以了
            .option("subscribe","test")  //指定topic
            .option("failOnDataLoss",false)  //如果读取数据源时,发现数据突然缺失,比如被删,则是否马上抛出异常
            .option("fetchOffset.numRetries",3)  //获取消息的偏移量时,最多进行的重试次数
            .option("maxOffsetsPerTrigger",500)/**用于限流,限定每次读取数据的最大条数,不指定则是as fast as possible*/
            .option("startingOffsets","earliest")  //第一次消费时,读取kafka数据的位置
            .load()

        import spark.implicits._
        val ds = rawDF.selectExpr("CAST(value AS STRING)")  //将kafka中的数据的value转为为string,原始为binary类型
                        .map(row => {
                            val line = row.getAs[String]("value") //获取row对象中的field,其实也只有一个field
                            val rawJson = JSON.parseObject(line)      //原始string是一个json,对其进行解析
                            val message = rawJson.getString("message")  //获取业务数据部分
                            val msgArray = message.split(",")  //指定分隔符进行字段切分
                            msgArray
                        }).filter(_.length == 9)  //只留字段数为9的数据
                        .filter(array => array(2).length >= 8)//确保日期字段符合规范
                        .map(array => (array(0)+array(1)+array(2),array(0),array(1),array(2),array(3),
                if(array(4).isInstanceOf[Int]) array(4).toInt else 99,array(5),array(6),array(7),array(8),array(2).substring(0,4),array(2).substring(4,6),array(2).substring(6,8))) //将其转化成为元组,为了方便下一步赋予schema
                        .toDF("id","client_ip","domain","time","target_ip","rcode","query_type","authority_record","add_msg","dns_ip","year","month","day") //给裸数据添加字段名

        ds.printSchema() //打印schema,确认没有问题

        val query = ds.writeStream
            .outputMode(OutputMode.Append())  //指定数据的写入方式
                .format("orc")  //指定外部输出的文件存储格式
                .option("format", "append")
                .trigger(Trigger.ProcessingTime(10,TimeUnit.SECONDS))/**每60秒执行一次,不指定就是as fast as possible*/
                .option("checkpointLocation","hdfs:/xxxxx:8020/tmp/offset/test/StructuredStreamingFromKafka2Hive") /**用来保存offset,用该目录来绑定对应的offset,如果该目录发生改变则程序运行的id会发生变化,类比group.id的变化,写hive的时候一定不要轻易变动*/
                .partitionBy("year","month","day")//提供分区字段
                .toTable("test.test")//写入hive表
        query.awaitTermination()
    }

}

看着好像很简单对不对,但是这里面其实有很多坑,而关于structured streaming写hive动态分区表的文章很少,只能通过看官方文档,以及源码解释来一步步实验。

为了方便,还是以一镜到底的方式把全部逻辑都写在main方法中,但是如果是在生产环境中,千万别这么干,容易被你的leader揍。


具体的坑,我都给你做了记录,避免你重复踩,下面会讲到。

代码对应的maven依赖如下:
<dependencies>
        <!--主要提供SparkConf和SparkContext对象-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--提供SparkSession对象-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.12</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--spark读取kafka的connector-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql-kafka-0-10_2.12</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--通过json转换-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.71</version>
        </dependency>
        <!--提供spark读取hive-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-hive_2.12</artifactId>
            <version>3.2.0</version>
        </dependency>
    </dependencies>



|运行结果
先看写入到分区表后,该表的属性和创建语句:


注意:这个建表是程序自动创建的,不是我事先建的。

再来看分区数据在hdfs上的分布情况:


可以看到,数据是实实在在写到hive的分区表中去了的,且效率很高。



|遇到的坑

1,用这种方式去写hive表,这个表不能提前用hive命令行去创建,否则会报错说表属性不兼容;

2,数据写入到hive表之后,无法直接用hive命令行的命令进行查看(具体解决办法还在探索中),但可以用spark程序进行读取;

3,在数据写入hive分区表中过程中,一定要指定checkpointLocation,且中间最好不要修改(可以删除重跑数据),否则容易出现数据写入过程中断的情况;


|最后

任何新的技术或者api,在第一次使用的时候一定会遇到很多坑,这是每一个学大数据人的必经之路,能否坚持住并趟过这些坑,是你水平增长的关键。




我这有一个高质量的大数据学习讨论群,不怎么扯淡、偶尔活跃气氛、大部分时间都在讨论技术,你在群里的几乎任何大数据相关的问题,都可以得到解答,要不要进来试试呢?拉你进群啊...


原文作者:安瑞哥是码农;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-02-16

  • 博文量
    228
  • 访问量
    115433