数据一致性

SparkStreaming与Kafka

Spark Streaming提供的零数据丢失机制需要满足以下几个先决条件:

  1. 输入的数据来自可靠的数据源和可靠的接收器
  2. 应用程序的metadata被application的driver持久化了(checkpointed ),元数据持久化(Metadata checkpointing)
  3. 启用了WAL特性(Write ahead log)

可靠的数据源和可靠的接收器

  对于Kafka输入数据源,Spark Streaming可以对已经接收的数据进行确认。输入的数据首先被接收器(receivers )所接收,然后存储到Spark中(默认情况下,数据保存到2个执行器中以便进行容错)。数据一旦存储到Spark中,接收器可以对它进行确认(比如,如果消费Kafka里面的数据时可以更新Zookeeper里面的偏移量)。这种机制保证了在接收器突然挂掉的情况下也不会丢失数据:因为数据虽然被接收,但是没有被持久化的情况下是不会发送确认消息的。所以在接收器恢复的时候,数据可以被原端重新发送
在这里插入图片描述

元数据持久化(Metadata checkpoint)

  可靠的数据源和接收器允许从接收器挂掉的情况下恢复(或者是接收器运行的Exectuor和服务器挂掉都可以)。但是如果Driver挂掉如何恢复?可以对应用程序的元数据进行Checkpoint。利用这个特性,Driver可以将应用程序的重要元数据持久化到可靠的存储中(HDFS);然后Driver可以利用这些持久化的数据进行恢复。元数据包括:
配置、代码、那些在队列中还没有处理的batch(仅仅保存元数据,而不是这些batch中的数据)
在这里插入图片描述
由于有了元数据的Checkpoint,所以Driver可以利用它们重构应用程序,而且可以计算出Driver挂掉的时候应用程序执行到什么位置

WAL(Write ahead log)

【可能存在数据丢失的场景】
  即使是可靠的数据源、可靠的接收器和对元数据进行Checkpoint,仍然不足以阻止潜在的数据丢失。例如:两个Exectuor已经从接收器中接收到输入数据,并将它缓存到Exectuor的内存中,接收器通知输入源数据已经接收,Exectuor根据应用程序的代码开始处理已经缓存的数据,这时候Driver突然挂掉了,从设计的角度看,一旦Driver挂掉之后,它维护的Exectuor也将全部被kill,既然所有的Exectuor被kill了,所以缓存到它们内存中的数据也将被丢失77. 缓存的时候不可能恢复,因为它们是缓存在Exectuor的内存中,所以数据被丢失了。

  为了解决上面提到的糟糕场景,Spark Streaming 1.2开始引入WAL机制。启用了WAL机制,所以已经接收的数据被接收器写入到容错存储中(HDFS)。由于采用了WAl机制,Driver可以从失败的点重新读取数据,即使Exectuor中内存的数据已经丢失了。在这个简单的方法下,Spark Streaming提供了一种即使是Driver挂掉也可以避免数据丢失的机制。
在这里插入图片描述

At-least-once语义

  虽然WAL可以确保数据不丢失,它并不能对所有的数据源保证exactly-once语义。可能发生在Spark Streaming整合Kafka的糟糕场景:【1】接收器接收到输入数据,并把它存储到WAL中 【2】接收器在更新Zookeeper中Kafka的偏移量之前突然挂掉了。
在这里插入图片描述
【3】Spark Streaming假设输入数据已成功收到(因为它已经写入到WAL中),然而Kafka认为数据被没有被消费,因为相应的偏移量并没有在Zookeeper中更新。过了一会,接收器从失败中恢复。那些被保存到WAL中但未被处理的数据被重新读取 【4】一旦从WAL中读取所有的数据之后,接收器开始从Kafka中消费数据。因为接收器是采用Kafka的High-Level Consumer API实现的,它开始从Zookeeper当前记录的偏移量开始读取数据,但是因为接收器挂掉的时候偏移量并没有更新到Zookeeper中,所有有一些数据被处理了2次。

除了上面描述的场景,WAL还有其他两个不可忽略的缺点:
  WAL减少了接收器的吞吐量,因为接受到的数据必须保存到可靠的分布式文件系统中。对于一些输入源来说,它会重复相同的数据。比如当从Kafka中读取数据,你需要在Kafka的brokers中保存一份数据,而且你还得在Spark Streaming中保存一份。

Kafka direct API

  为了解决由WAL引入的性能损失,并且保证 exactly-once 语义,Spark Streaming 1.3中引入了名为Kafka direct API。Spark driver只需要简单地计算下一个batch需要处理Kafka中偏移量的范围,然后命令Spark Exectuor直接从Kafka相应Topic的分区中消费数据。换句话说,这种方法把Kafka当作成一个文件系统,然后像读文件一样来消费Topic中的数据。
在这里插入图片描述

在这个简单但强大的设计中:

  • 不再需要Kafka接收器,Exectuor直接采用Simple Consumer API从Kafka中消费数据
  • 不再需要WAL机制,我们仍然可以从失败恢复之后从Kafka中重新消费数据
  • exactly-once语义得以保存,我们不再从WAL中读取重复的数据

Kafka

在这里插入图片描述
  假设分区的副本为3,其中副本0是 Leader,副本1和副本2是 follower,并且在 ISR 列表里面。虽然副本0已经写入了 Message4,但是 Consumer 只能读取到 Message2。因为所有的 ISR 都同步了 Message2,只有 High Water Mark 以上的消息才支持 Consumer 读取,而 High Water Mark 取决于 ISR 列表里面偏移量最小的分区,对应于上图的副本2,这个很类似于木桶原理。
  这样做的原因是还没有被足够多副本复制的消息被认为是“不安全”的,如果 Leader 发生崩溃,另一个副本成为新 Leader,那么这些消息很可能丢失了。如果我们允许消费者读取这些消息,可能就会破坏一致性。试想,一个消费者从当前 Leader(副本0) 读取并处理了 Message4,这个时候 Leader 挂掉了,选举了副本1为新的 Leader,这时候另一个消费者再去从新的 Leader 读取消息,发现这个消息其实并不存在,这就导致了数据不一致性问题。
  当然,引入了 High Water Mark 机制,会导致Broker间的消息复制因为某些原因变慢,那么消息到达消费者的时间也会随之变长(因为我们会先等待消息复制完毕)。延迟时间可以通过参数 replica.lag.time.max.ms 参数配置,它指定了副本在复制消息时可被允许的最大延迟时间。

Partition存储结构
  每个Partition分为多个Segment,每个Segment包含两个文件:log文件和index文件,分别命名为start_offset.log和start_offset.index。log文件包含具体的msg数据,每条msg会有一个递增的offset。Index文件是对log文件的索引:每隔一定大小的块,索引msg在该segment中的相对offset和在log文件中的位置偏移量

根据offset查找msg的过程
  根据msg的offset和log文件名中的start_offset,找到最后一个不大于msgoffset的segment,即为msg所在的segment;根据对应segment的index文件,进一步查找msg在log文件中的偏移量。从log文件的偏移量开始读取解析msg,比较msgoffset,找到所要读取的msg

Partition recovery过程
  每个Partition会在磁盘记录一个RecoveryPoint, 记录已经flush到磁盘的最大offset。当broker fail 重启时,会进行loadLogs。首先会读取该Partition的RecoveryPoint,找到包含RecoveryPoint的segment及以后的segment, 这些segment就是可能没有完全flush到磁盘segments。然后调用segment的recover,重新读取各个segment的msg,并重建索引。这样做的优点:

  • 以segment为单位管理Partition数据,方便数据生命周期的管理,删除过期数据简单
  • 在程序崩溃重启时,加快recovery速度,只需恢复未完全flush到磁盘的segment
  • 通过命名中offset信息和index文件,大大加快msg查找时间,并且通过分多个Segment,每个index文件很小,查找速度更快

Partition Replica同步机制

  • Partition的多个replica中一个为Leader,其余为follower
  • Producer只与Leader交互,把数据写入到Leader中
  • Followers从Leader中拉取数据进行数据同步
  • Consumer只从Leader拉取数据

ISR:所有不落后的replica集合, 不落后有两层含义:距离上次FetchRequest的时间不大于某一个值或落后的消息数不大于某一个值, Leader失败后会从ISR中选取一个Follower做Leader

数据一致性保证
  数据一致性定义:若某条消息对client可见,那么即使Leader挂了,在新Leader上数据依然可以被读到。

  • HighWaterMark简称HW: Partition的高水位,取一个partition对应的ISR中最小的LEO作为HW,消费者最多只能消费到HW所在的位置,另外每个replica都有highWatermark,leader和follower各自负责更新自己的highWatermark状态,highWatermark <= leader. LogEndOffset
  • 对于Leader新写入的msg,Consumer不能立刻消费,Leader会等待该消息被所有ISR中的replica同步后,更新HW,此时该消息才能被Consumer消费,即Consumer最多只能消费到HW位置

这样就保证了如果Leader Broker失效,该消息仍然可以从新选举的Leader中获取。对于来自内部Broker的读取请求,没有HW的限制。同时,Follower也会维护一份自己的HW,Folloer.HW = min(Leader.HW, Follower.offset)

数据可靠性保证
  当Producer向Leader发送数据时,可以通过acks参数设置数据可靠性的级别

  • 0: 不论写入是否成功,server不需要给Producer发送Response,如果发生异常,server会终止连接,触发Producer更新meta数据
  • 1: Leader写入成功后即发送Response,此种情况如果Leader fail,会丢失数据
  • -1: 等待所有ISR接收到消息后再给Producer发送Response,这是最强保证

仅设置acks=-1也不能保证数据不丢失,当Isr列表中只有Leader时,同样有可能造成数据丢失。要保证数据不丢除了设置acks=-1, 还要保证ISR的大小大于等于2,具体参数设置:

request.required.acks:设置为-1 等待所有ISR列表中的Replica接收到消息后采算写成功;
min.insync.replicas: 设置为大于等于2,保证ISR中至少有两个Replica
Producer要在吞吐率和数据可靠性之间做一个权衡

ZooKeeper

参见:ZooKeeper保持一致性原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值