Spark Structured Streaming - 处理每一行

时间:2017-05-25 18:18:37

标签: scala apache-spark apache-kafka spark-structured-streaming

我正在使用Spark 2.1.1进行结构化流式传输。我需要将一些业务逻辑应用于传入的消息(来自Kafka源)。

基本上,我需要获取消息,获取一些关键值,在HBase中查找它们并在数据集上执行更多的业务逻辑。最终结果是需要写入另一个Kafka队列的字符串消息。

但是,由于传入消息的抽象是一个数据帧(无界表 - 结构化流),我必须遍历通过mapPartitions触发的数据集(由于HBase客户端不可序列化而导致的分区)。

在我的过程中,我需要遍历每一行以执行相同的业务流程。

  1. 是否有更好的方法可以帮助我避免dataFrame.mapPartitions来电?我觉得它是连续的和迭代的!!
  2. 结构化流式传输基本上迫使我从业务流程中生成输出数据框,而没有任何开始。我可以用什么其他设计模式来实现我的最终目标?
  3. 您会推荐另一种方法吗?

2 个答案:

答案 0 :(得分:6)

当你谈到在Spark中使用Dataframes时,非常广泛地说,你可以做三件事之一 a)生成数据帧 b)转换数据帧 c)使用数据框

在结构化流式传输中,使用 DataSource 生成流式DataFrame。通常,您使用公开的 sparkSession.readStream 方法创建源。此方法返回一个DataStreamReader,它有多种方法可以从各种输入中读取。所有这些都返回一个DataFrame。在内部,它创建了一个DataSource。 Spark允许您实现自己的DataSource,但他们建议不要使用它,因为从2.2开始,界面被认为是实验性的

您主要使用map或reduce或使用spark SQL转换数据框。有不同风格的map(map,mapPartition,mapParititionWithIndex)等等。所有这些都基本上占了一排并返回一行。内部Spark执行并行调用map方法的工作。它对数据进行分区,在集群上的执行程序中进行传播,并在执行程序中调用map方法。你不必担心并行性。它是在引擎盖下建造的。 mapParitions不是"顺序"。是的,分区中的行是按顺序执行的,但是并行执行多个分区。您可以通过对数据帧进行分区来轻松控制并行度。你有5个分区,你将有5个进程并行运行。你有200个,如果你有200个核心,你可以有200个并行运行

请注意,没有什么可以阻止您外出到管理转换中的状态的外部系统。 但是,您的转换应该是幂等的。给定一组输入,它们应始终生成相同的输出,并使系统随时间保持相同的状态。如果您在转换中与外部系统交谈,这可能会很困难。结构化流媒体至少提供一次保证。这意味着可以多次转换同一行。因此,如果您正在执行诸如向银行帐户添加资金之类的操作,您可能会发现您已将两次相同数额的资金添加到某些帐户中。

数据由接收器消耗。通常,通过在Dataframe上调用format方法然后调用start来添加接收器。 StructuredStreaming有一些内置的接收器(除了一个)或多或少没用。你可以创建自定义接收器,但不建议再次使用,因为接口是实验性的。唯一有用的接收器就是你要实现的。它被称为ForEachSink。 Spark会使用分区中的所有行调用中的每个接收器。您可以使用行执行任何操作,包括将其写入Hbase。请注意,由于结构化流的至少一次性质,同一行可能会多次送入ForEachSink。您需要以幂等方式实现它。此外,如果您有多个接收器,则数据将并行写入接收器。您无法控制接收器的调用顺序。可能发生一个接收器从一个微批次获取数据而另一个接收器仍在处理前一个微批次的数据。从本质上讲,接收器最终是一致的,而不是立即保持一致。

通常,构建代码最简洁的方法是避免转换到转换中的外部系统。您的转换应该纯粹转换数据帧中的数据。如果您需要来自HBase的数据,请将其放入数据框,将其与流数据框连接,然后进行转换。这是因为当你去外部系统时,它很难扩展。您希望通过增加数据框上的分区和添加节点来扩展转换。但是,与外部系统通信的节点太多会增加外部系统的负载并导致瓶颈。从数据检索中分离转换可以让您独立扩展它们。

BUT !!!!这里有很大的问题......

1)当你谈到结构化流媒体时,没有办法实现一个可以根据你输入中的数据从HBase中有选择地获取数据的Source。你必须在map(-like)方法中执行此操作。所以,IMO,如果Hbase中的数据发生变化或者您不想保留在内存中的大量数据,那么您所拥有的就完全没问题了。如果您在HBase中的数据很小且不变,那么最好将其读入批处理数据框,对其进行缓存,然后将其与流数据框连接。 Spark会将所有数据加载到自己的内存/磁盘存储中,并将其保留在那里。如果您的数据很小且频繁更改,则最好在数据框中读取数据,不要对其进行缓存并将其与流数据框连接。每次运行微批时,Spark都会从HBase加载数据。

2)没有办法订购2个独立的接收器。因此,如果您的要求要求您写入数据库并写入Kafka,并且您希望保证在数据库中提交行之后写入Kafka中的行,那么唯一的方法是 a)在每个接收器中进行两次写入。 b)在类似地图的函数中写入一个系统,在每个接收器中写入另一个系统

不幸的是,如果您有要求从流源读取数据的要求,请将其与来自批处理源的数据连接,转换,将其写入数据库,调用API,从API获取结果并编写对Kafka的API的结果,并且这些操作必须按照确切的顺序完成,那么你可以做到这一点的唯一方法是在转换组件中实现接收器逻辑。您必须确保将逻辑保持在单独的映射函数中,以便以最佳方式对它们进行并行化。

此外,没有什么好方法可以知道您的应用程序何时完全处理微批次,特别是如果您有多个接收器

答案 1 :(得分:0)

尝试 ForeachWriter,在 ForeachWriter process() 方法中从数据帧接收单行。 您可以根据需要处理数据。 https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/ForeachWriter.html