网上查找的kafka通过spark streaming落地到HIVE的方案一般都是Scala写的,为此碰到了很多的坑,特此记录一下使用pyspark来实现实时落地到HIVE的方案

说在前面

spark Streaming 接受kafka的数据落地HIVIE有2个原生的问题

  1. 偏移量管理问题:一般建议都是使用直连的方式连接kafka,而不是接收器的方式,所以需要直接来管理偏移量
  2. 小文件问题:每个batch都会产生和kafka partition个数等同的parquet,如果直接落地的话会产生很多小文件,导致数据查询缓慢和文件系统崩溃

知识面准备

  1. spark的streaming官方文档建议先看一下:http://spark.apache.org/docs/2.2.1/streaming-programming-guide.html 至少有个基本的了解。
  2. 还有kafka的集成文档:http://spark.apache.org/docs/2.2.1/streaming-kafka-integration.html, 这里有2个版本,分别是0.8和1.0,1.0那个不支持Python就不用看了。
  3. 以及pyspark中的streaming文档:http://spark.apache.org/docs/2.2.1/api/python/pyspark.streaming.html,重点看一下kafkaUtil模块

环境准备

  1. 需要确认自己的spark版本,因为需要下载指定的依赖jar包才能正常的使用streaming,我的是spark2.2.1的,所以至少需要以下jar包:
--jars org.scala-lang_scala-reflect-2.10.4.jar,\
org.slf4j_slf4j-api-1.7.7.jar,\
com.typesafe.scala-logging_scala-logging-api_2.10-2.1.2.jar,\
com.typesafe.scala-logging_scala-logging-slf4j_2.10-2.1.2.jar,\
spark-streaming-kafka-0-8_2.11-2.2.1.jar,\
spark-streaming-kafka-0-8-assembly_2.11-2.2.1.jar

这些jar包都可以通过maven的库去下载,例如:https://mvnrepository.com/artifact/org.apache.spark/spark-streaming-kafka-0-8_2.11/2.2.1 是下载park-streaming-kafka-0-8_2.11-2.2.1.jar,其他的类似,自己在maven库里面搜一下下载好,在spark-submit的时候需要引用进来。一定要注意版本,版本不对会报莫名的错误,我之前就是用来2.3.1的版本,代码一直调试不通。

代码流程

  • 主流程:
def kafka_to_hive():
    offset_range = get_offset()
    if offset_range is None:
        kafka_stream = KafkaUtils.createDirectStream(ssc, [topics], kafkaParams=kafka_param)
    else:
        kafka_stream = KafkaUtils.createDirectStream(ssc, [topics], kafkaParams=kafka_param, fromOffsets=offset_range)

    # store offset
    kafka_stream.transform(store_offset_ranges).foreachRDD(process)

    ssc.start()
    ssc.awaitTermination()
    ssc.stop()

先去获取偏移量,这里的偏移量我是存在hdfs上的,从网上说的来看,可以存在三个地方,分别是交给ZK管理,交个kafka管理,或者使用spark的checkpoint。我觉得还是自己存个文件方便,就直接以json的格式存在hdfs上,process函数里面就是你自己的处理逻辑了,可以最终将rdd转成df存入hive中

  • 偏移量获取
def save_offset(_):
    offset_df = sql_context.createDataFrame(offsetRanges, offset_schema)
    offset_df.repartition(1).write.mode('overwrite').json(offset_file)
    
def store_offset_ranges(rdd):
    global offsetRanges
    offsetRanges = rdd.offsetRanges()
    return rdd
    
def assemble_offset(x):
    topic = x['topic']
    partition = x['partition']
    start_point = x['untilOffset']
    return {TopicAndPartition(topic, partition): start_point}


def get_offset():
    offset_df = sql_context.read.json(offset_file, schema=offset_schema)
    if offset_df.rdd.isEmpty():
        return None
    else:
        offset_list = offset_df.rdd.map(lambda row: assemble_offset(row)).collect()
        offset_dict = {}
        for i in offset_list:
            for k, v in i.items():
                offset_dict[k] = v
        return offset_dict

偏移量获取需要使用TopicAndPartition这个类来做key,偏移量本身作为value来构造一个字典,如果多个partition,就是构造一个大字典

启动

  • 启动命令:
spark-submit --master yarn-client \
--executor-cores 4 \
--num-executors 20 \
--queue queue_0011_03 \
--executor-memory 5g \
--driver-memory 1g \
--conf spark.sql.shuffle.partitions=200 \
--conf spark.rdd.compress=true \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
--conf spark.streaming.stopGracefullyOnShutdown=false \
--conf spark.streaming.backpressure.enabled=true \
--conf spark.streaming.kafka.maxRatePerPartition=50 \
--files /appcom/spark-2.2.1-config/fairscheduler.xml,/appcom/hive-config/hive-site.xml \
--jars org.scala-lang_scala-reflect-2.10.4.jar,\
org.slf4j_slf4j-api-1.7.7.jar,\
com.typesafe.scala-logging_scala-logging-api_2.10-2.1.2.jar,\
com.typesafe.scala-logging_scala-logging-slf4j_2.10-2.1.2.jar,\
spark-streaming-kafka-0-8_2.11-2.2.1.jar,\
spark-streaming-kafka-0-8-assembly_2.11-2.2.1.jar test_kafka.py

这里面的几个关键参数说明一下:

  • spark.rdd.compress: 开启rdd的压缩,这样传输会快点
  • spark.serializer:使用kryo序列化方式,说是这样rdd占用的容量会更小
  • spark.streaming.stopGracefullyOnShutdown:如果优雅的关闭streaming流,启用了这个后,可以通过命令行在driver端关闭streaming进程而不会丢失数据
  • spark.streaming.backpressure.enabled:反压功能,可以在生成端积压过多数据后减少单批数据量以加速处理过程
  • spark.streaming.kafka.maxRatePerPartition:指定每partition每秒的最大处理数据行数,这样防止在停止运行很久后再启动时,第一个batch的数据过大导致数据积压