python 读取kafka 写hive_使用 spark 从 kafka 消费数据写入 hive 动态分区表(一)

使用 spark 从 kafka 消费数据写入 hive 动态分区表

最近因为业务需求,需要把 kafka 内的数据写入 hive 动态分区表,进入 kafka 的数据是保证不会重复的,同一条业务数据仅会进入 kafka 一次。这就保证数据到了 hive 基本不会发生 update 操作,可以对 hive 进行统计生成静态表的形式将统计数据写入 MySQL。咱也不说那么多废话了,开整。

直接写入

从 kafka 取出数据转化成 bean 对象,根据业务要求将数据过滤,清洗,拿到最终的 RDD,直接写入 hive 分区表。(PS:我就不贴全部代码,仅仅贴出主要代码,作为一个有职业道德的程序员,职业道德不允许我这么做。哈哈哈,其实是我怂,我这么大条,万一泄露公司机密就 OVER 了)

RDD 写入 HIVE 动态分区表,因为我需要向两个动态分区表写数据,所以加了 type 字段

private static void writeHive(SparkSession sparkSession,JavaRDD rdd,String type){

Dataset writerTradeData = sparkSession.createDataFrame(rdd,type.equals("trade")?ApiTradePlus.class:ApiTradeOrderPlus.class);

writerTradeData.createOrReplaceTempView("tmp"+type);

String sql = "";

if(type.equals("trade")){

sql = "insert into tmp_trade `partition(tradedate)` select * from "+"tmp"+type;

}

else if(type.equals("order")){

sql = "insert into tmp_trade_order `partition(tradedate)` select * from "+"tmp"+type;

}

sparkSession.sql(sql);

}

别忘记最重要的 hive 操作,默认是不支持动态分区表需要开启配置。以 hive.exec.开头的几个参数,前两个开启动态分区表的配置,后三个是设置分区数上限,我是按照日期分的,一不留神就容易超出分区表数上限,所以设置的稍微大了一点点。

SparkConf sparkConf = SparkFactory.getDefaultSparkConf()

.set("spark.executor.cores", "4")

.set("spark.ui.port", "30004")

.set("hive.exec.dynamic.partition.mode","nonstrict")

.set("hive.exec.dynamic.partition","true")

.set("hive.exec.max.dynamic.partitions.pernode","100000")

.set("hive.exec.max.dynamic.partitions","100000")

.set("hive.exec.max.created.files","100000")

.setAppName("KafkaToHiveStreaming");

哦,对了还有这个我写的 sparkConf 工厂类,生成默认配置,速率设置了 100,因为有 15 个分区,10s 执行一次,所以执行一次就会消费 1.5W 的消息,自我觉得应该能够 10s 应该够了。

public static SparkConf getDefaultSparkConf() {

return new SparkConf()

.set("spark.executor.memory", "6g")

.set("spark.shuffle.file.buffer", "1024k")

.set("spark.reducer.maxSizeInFlight", "128m")

.set("spark.shuffle.memoryFraction", "0.3")

.set("spark.streaming.stopGracefullyOnShutdown", "true")

.set("spark.streaming.kafka.maxRatePerPartition", "100")

.set("spark.serializer", KryoSerializer.class.getCanonicalName())

.registerKryoClasses(SERIALIZER_CLASS);

}

接下来激动人心的时刻到了。把任务提交到 spark 集群,当我满心欢喜提上去之后,打开监控页面,心瞬间就凉了一半,提交的第一个任务,执行了整整 10 分钟,我已经不忍心截图了,悲伤辣么大。

10 分钟处理了 1.5Wkafka 消息,写入数据库 2W 左右,因为业务需求,需要特殊记录写两次,位于的分区不一样。

曲线救国

先写入 hive 非分区表,然后再通过 hive 将非分区表的数据迁移到分区表上。

直接写分区表的速度真的是慢的可以,如果放到线上,我估计就要被离职了。为了我 Money 还是要加油呀。所以想出了这个曲线救国的路线,其实刚开始是想写 hadoop 文件,然后使用外部表,外部表数据源选择 hadoop 文件,然后通过 insert into select * from 导入。转念一想不如直接写非分区表,这样能直接生成格式化号的 hadoop 文件,还不用外部表。

所以原来的一个 spark 任务分成了两个 spark 任务:

SparkStreaming 任务:负责消费 kafka 数据写入非分区表。

SparkSql 任务:负责将 hive 的非分区表的数据迁移到动态分区表。

SparkStreaming 关键代码如下:

private static void writeHive(SparkSession sparkSession,JavaRDD rdd,String time,String type){

Dataset writerTradeData = sparkSession.createDataFrame(rdd,type.equals(TABLE_API_TRADE)?ApiTradeWithoutOrder.class:ApiTradeOrderWithTime.class);

writerTradeData.createOrReplaceTempView("tmp"+time);

String sql = "";

String selectSql = null;

/**

* select 的顺序要求与hive中desc表的字段顺序一致

*/

if(type.equals(TABLE_API_TRADE)){

selectSql = " select 全部字段(最好不要用*) from tmp"+time;

sql = String.join(" ","insert into ",TABLE_API_TRADE) ;

}

else if(type.equals(TABLE_API_TRADE_ORDER)) {

selectSql=" select 全部字段(最好不要用*) from tmp" + time;

sql = String.join(" ","insert into ",TABLE_API_TRADE_ORDER) ;

}

sparkSession.sql(String.join(" ",sql,selectSql));

}

简单说明一下,这个地方 time 是干啥的,其实这个对应分区表的分区字段,还对代码进行了简单的优化,毕竟公司最近在实行阿里编码规范。这个写非分区表是真的快,嗖嗖嗖的。1.5W/7s,这个 7s 还包含了清洗过滤数据的过程,基本没变。

SparkSql 的关键代码:

private static void deal() {

String insertSql = " insert into ";

String partitionSql = " partition(tradedate) ";

String tradeSelectSql = " select 全部字段(最好不要用*) from " + TABLE_API_TRADE;

String orderSelectSql = " select 全部字段(最好不要用*) from " + TABLE_API_TRADE_ORDER;

/**

* 1. 迁移数据 insert into select from

* 2. 重建非分区表 truncate table

* point: select 的顺序要求与hive中desc表的字段顺序一致tradeSelectSql,orderSelectSql

*/

session.sql("USE ".concat(HIVE_DB));

session.sql(String.join(" ",insertSql, API_TRADE_PARTITIONED_TABLE_NAME,partitionSql,tradeSelectSql));

session.sql(String.join(" ",insertSql, API_TRADE_ORDER_PARTITIONED_TABLE_NAME,partitionSql,orderSelectSql));

session.sql("truncate table" + TABLE_API_TRADE);

session.sql("truncate table" + TABLE_API_TRADE_ORDER);

}

说白了,这个就是使用 insert into partition(分区字段) select * from

hadoop 崩了

当我满心欢喜的执行 SparkSql 任务的时候,又有噩耗发生了,hadoop 竟然扛不住 hive 表数据迁移,从非分区表写入到分区表,数据总量大约 2000W 左右吧,分了 1.8W 个 task,在执行到 0.8W 的时候,hadoop 第一个 NameNode 崩了,1.4W 的时候第二个 NameNode 崩了,我感觉我也崩了,哎,花了 10 分钟重启全部集群,还是规划好每次写入数据的条数吧,最后调整到了 600W 进行一次 hive 数据迁移。

hive 就会搞事情

你以为迁移完成就完了嘛?NO!NO!NO!我不得统计 hive 数据的条数呀,很不幸的是 hive 有个配置,默认不不开启,所以你的 select count(1) from table 和上帝一样给你开了个玩笑,返回 0,我忙活了这么久你告诉我一条都没写进去。感觉自己和蔡徐坤一样,会唱,会跳,会 rap,会打篮球,就是不会写程序。乖乖的在 hive-env.sh 中加了

# hive shell sql 爆内存溢出可以增大这个值

export HADOOP_HEAPSIZE=4096

重点来了(敲黑板,记到小本本上)

重点来了,因为数据中可能存在\t\r\n 等鬼东西,所以做了替换,并且在 hive 中的字段分割符使用\001,我就不信了用户数据的东西还有\001。最恶心的是\r\n 会影响 hive 的数据行数,是不是听上去有点懵逼?来我告诉你,hive 默认的行分割符是\n,所以如果你的字段里面包含\n,那么恭喜你中奖了,数据会出现裂变(不知道在这里合适不合适)一条变两条,贼开心,厉害点的会变成 10 条,我就出现了 1 变 10 的操作。 ,是谁录入的,让我出来打一顿,缺少社会主义的毒打。

其实吧,这个事要解决很简单,我使用\011 或者其他的作为行分割符不就行了嘛,但是很不幸的告诉你,hive 暂时仅支持\n 作为行分割符,难受的一批,乖乖的清洗数据去吧。

你可能感兴趣的:(python,读取kafka,写hive)