使用Structured Streaming消费Kafka数据

由于工作需要,在数据收集上牵扯到多个维度的爬虫数据。之前的流程是:爬虫工程师通过文件方式保存爬取的数据,交付给我们做数据清洗处理,再导入到数据库。为了降低交互过程中的时间成本,提高效率,我们开始引入流处理的方式。

之前的模式:

在这里插入图片描述

使用流处理之后的模式:

在这里插入图片描述

通过kafka-python包的生产者写入数据

首先,需要对爬虫脚本进行改造。原先的写入文件代码部分可以不需要改动,只要对爬取到的数据增加写入kafka操作即可(kafka的相关配置可见上一篇文章)。

爬虫数据可分为两种,一种是定期的全量数据更新,另一种是增量数据。对于全量数据更新,我们可以在写入kafka中增加时间戳来区分版本。

通过kafka-python包,可以很方便的写入数据到kafka中:

from kafka import KafkaProducer

producer = KafkaProducer(bootstrap_servers=['broker1:port', 'broker2:port', ...])
topic = "crawler"

# 写入kafka的数据需为二进制内容,所以需要进行转化。
# 对于object内容,可先通过json转化成JSON STRING,再用bytes转为二进制。
# spark有from_json函数可以转化JSON STRING
for i in range(100):
    producer.send(topic, bytes('some_message_bytes' + str(i), encoding="utf-8"))

参考资料:

Structured Streaming消费Kafka数据

Spark提供了很好的批流统一API,而最近刚推出的delta也是如此。这样,流处理也能受益于针对Dataframe的优化。

为了使用kafka数据源,需要加载相应的jar包,所以在启动pyspark或者是通过spark-submit提交时,需要加入相关依赖:

$ pyspark --packages org.apache.spark:spark-sql-kafka-0-10_2.11:2.4.3
$ spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.11:2.4.3

使用maven做项目管理时,可以把spark-sql-kafka加入到依赖中

<!-- https://mvnrepository.com/artifact/org.apache.spark/spark-sql-kafka-0-10 -->
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql-kafka-0-10_2.12</artifactId>
    <version>2.4.3</version>
    <scope>provided</scope>
</dependency>

使用readStream来读入流,指定format为kafka,kafka的broker配置以及绑定的主题(可以绑定多个主题)。还可以指定offset的位置(有latest, earliest以及具体对每一个topic的每一个分区进行指定)。

df = spark \
  .readStream \
  .format("kafka") \
  .option("kafka.bootstrap.servers", "host1:port1,host2:port2") \
  .option("subscribe", "topic1,topic2") \
  .option("startingOffsets", """{"topic1":{"0":23,"1":-2},"topic2":{"0":-2}}""") \
  .load()

这样子,获得到的dataframe就可以使用DataFrame相关的操作。得到清洗后的数据就可以写入到sink。spark常用的文件格式为parquet,有关parquet的介绍可以参考后面的链接资料。

# 写入到parquet文件
df.writeStream.format("parquet"). \
    option("path", "save_path"). \
    option("checkpointLocation", "save_path/checkpoints"). \
    start()

大家可能会困惑Structured Streaming是怎么对kafka的offset进行管理。我们看到读入流的时候要设置offset,那么如果程序中断之后再重启会是怎样呢?

这里,我们注意到流写入到sink的时候,必须要设置一个checkpointLocaion,Structured Streaming就是在这个目录下来管理offset。如果程序中断之后再重启,虽然在读入流的时候设置的是某一个offset,但是在写入流的时候,如果已经存在了checkpointLocation,那么流会从之前中断的地方继续处理,即读入流对offset的设置只是针对checkpointLocation第一次初始化的时候有效。

在实际使用Structured Streaming的时候,我们也遇到了一些问题:

  • 对于长期运行的Structured Streaming程序,如何做到动态使用资源

    首先先评估是否有必要使用长期运行的streaming程序,如果对数据实时性要求没那么高,可以考虑做定期的流任务。如果需要长期运行,可以考虑spark的动态分配资源选项(听闻bug比较多):

    --conf spark.dynamicAllocation.enabled=true \
    --conf spark.dynamicAllocation.initialExecutors=2 \
    --conf spark.dynamicAllocation.minExecutors=2 \
    --conf spark.dynamicAllocation.maxExecutors=5
    
  • 使用Structured Streaming写入parquet文件时,会导致产生很多小的parquet文件,这样子对HDFS的namenode压力比较大

    可以参考这篇文章, 主要的两个解决方案是:

    1. 在Structured Streaming中通过coalesce来减少分区,从而减少写入的parquet文件数量
    2. 通过运行一个批处理程序来读入多个小parquet文件,通过repartition为指定数量后再写入parquet文件

参考资料:

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值