一.背景
Spark的RDD的基本容错语义:
- RDD是一个不变的,确定性可重新计算的分布式数据集。每个RDD都会记住在容错输入数据集上用于创建它的确定性操作的血缘关系。
- 如果由于工作节点故障而导致RDD的任何分区丢失,则可以使用操作血缘关系从原始的容错数据集中重新计算该分区。
- 假设所有RDD转换都是确定性的,则最终转换后的RDD中的数据将始终相同,而不管Spark集群中的故障如何。
Spark在容错文件系统【例如HDFS或S3】中的数据上运行。因此,从容错数据生成的所有RDD也是容错的。但是,Spark Streaming并非如此,因为大多数情况下是通过网络接收数据的【fileStream除外】。为了对所有生成的RDD实现相同的容错属性,将接收到的数据复制到集群中工作节点中的多个Spark执行程序中【默认复制因子为2】。这导致系统中发生故障时需要恢复的两种数据:
4. 接收和复制的数据:由于该数据的副本存在于其他节点之上,因此该数据在单个工作节点发生故障时仍可幸免。
5. 已接收但尚未进行复制的数据:由于未复制数据,因此恢复此数据的唯一方法是再次从源中获取数据。
此外,我们应该关注两种故障:
- 工作节点的故障:运行执行程序的任何工作节点都可能发生故障,并且这些节点上的所有内存中数据都将丢失。如果有任何接收器在发生故障的节点上运行,那么它们的缓冲数据将丢失。
- 驱动程序节点发生故障:如果运行Spark Streaming应用程序的驱动程序节点发生故障,则显然SparkContext会丢失,并且所有执行程序及其内存中的数据也会丢失。
二.定义
流系统的语义通常是根据每个记录可以被系统处理多少次来捕获的。系统在所有可能的操作条件下【尽管有故障等】可以提供三种保证:
- 最多一次:每个记录将被处理一次或完全不被处理。
- 至少一次:每个记录将被处理一次或多次。它比最多一次强,因为它确保不会丢失任何数据。但是可能会重复处理。
- 恰好一次【精确一次】:每个记录将被恰好处理一次,不会丢失任何数据,也不会多次处理任何数据。这显然是三者中最强有力的保证。
三.基本语义
概括地说,在任何流处理系统中,处理数据都需要三个步骤:
- 接收数据:使用接收器或其他方式从源接收数据。
- 转换数据:使用DStream和RDD转换对接收到的数据进行转换。
- 推送数据:将最终转换后的数据推送到外部系统,例如文件系统,数据库,仪表板等。
如果流应用程序必须实现端到端的精确一次保证,那么每个步骤都必须提供精确一次保证。也就是说,每条记录必须被接收一次,被转换一次,并被推送到下游系统一次。让我们在Spark Streaming的上下文中了解这些步骤的语义。
- 接收数据:不同的输入源提供不同的保证。
- 转换数据:由于RDD提供的保证,所有接收到的数据将只处理一次。即使出现故障,只要可以访问接收到的输入数据,最终转换后的RDD始终具有相同的内容。
- 推送数据:默认情况下,输出操作确保至少一次语义,因为它取决于输出操作的类型【是否为幂等】和下游系统的语义【是否支持事务】。但是用户可以实现自己的事务处理机制以实现一次语义。
四.接收数据的语义
不同的输入源提供不同的保证,范围从至少一次到恰好一次。
- 带文件
如果所有输入数据已经存在于诸如HDFS之类的容错文件系统中,则Spark Streaming始终可以从任何故障中恢复并处理所有数据。这提供了一次精确的语义,这意味着无论发生什么故障,所有数据都会被精确处理一次。 - 使用基于接收器的源
对于基于接收器的输入源,容错语义取决于故障情况和接收器类型。正如前面我们所讨论的,有两种类型的接收器:- 可靠的接收器:这些接收器仅在确保已复制接收到的数据后才确认可靠的来源。如果此类接收器发生故障,则源将不会收到对缓冲【未复制】数据的确认。因此,如果重新启动接收器,则源将重新发送数据,并且不会由于失败而丢失任何数据。
- 不可靠的接收器:此类接收器不发送确认,因此当由于工作程序或驱动程序故障而失败时,可能会丢失数据。
- 使用Kafka Direct API
在Spark 1.3中,我们引入了新的Kafka Direct API,可以确保Spark Streaming一次接收所有Kafka数据。同时,如果实施一次精确的输出操作,则可以实现端到端的一次精确保证。
根据所使用的接收器类型,我们可以实现以下语义。如果工作节点发生故障,那么使用可靠的接收器不会造成数据丢失。如果接收器不可靠,则接收到但未复制的数据可能会丢失。如果驱动程序节点发生故障,则除了这些丢失之外,所有已接收并复制到内存中的过去数据都将丢失。这将影响有状态转换的结果。
为了避免丢失过去收到的数据,Spark 1.2引入了预写日志,该日志将收到的数据保存到容错存储中。在启用预写日志和可靠的接收器的情况下,数据丢失为零。就语义而言,它至少提供了一次保证。
下表总结了失败时的语义:
五.输出操作的语义
输出操作【例如foreachRDD】至少具有一次语义,也就是说,在工作程序失败的情况下,转换后的数据可能多次写入到外部实体。尽管使用saveAs***Files操作将其保存到文件系统是可以接受的【因为文件将被相同的数据简单地覆盖】,但可能需要付出额外的努力才能实现一次语义。有两种方法。
- 幂等更新:多次尝试总是写入相同的数据。例如,saveAs***Files始终将相同的数据写入生成的文件。
- 事务性更新:所有更新都是以事务方式进行的,因此原子更新仅进行一次。一种做到这一点的方法如下。
- 使用批处理时间和RDD的分区索引来创建标识符。该标识符唯一地标识流应用程序中的Blob数据。
- 使用标识符以事务方式【即原子一次】更新与此Blob的外部系统。也就是说,如果尚未提交标识符,则自动提交分区数据和标识符。否则,如果已经提交,则跳过更新。