本期内容
-
exactly once
-
输入不重复
-
输出不重复
exactly once :有且仅被执行一次。(不多,不少,一次刚好)
首先和大家聊下概念:
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
简单记忆法则(一持原隔)
如:银行转账,A向B转账500元,这个步骤可以分为A扣500元,B加500元 两部分。
如果 A减500元成功后,在B加500元的时候失败了,那么A减掉的500就不生效。也就是说。要么两个操作都成功,要么两个操作都失败。
先了解下SparkStreaming的数据流转流程
数据一致性的要求:
-
数据源可靠。数据源产生出来后,万一crash要可以恢复。数据存在kafka是很好的选择,既可以存储,又可以做高吞吐消息队列
-
Receiver可靠。以数据来自Kafka为例。运行在Executor上的Receiver在接收到来自Kafka的数据时会向Kafka发送ACK确认收到信息并读取下一条信息,kafka会updateOffset来记录Receiver接收到的偏移,这种方式保证了在Executor数据零丢失。
-
Driver可靠。checkpoint可解决。
下面是几个数据非一致性的场景及解决方案
输入不丢失
数据丢失的场景:
在Receiver收到数据且通过Driver的调度,Executor开始计算数据的时候,如果Driver突然崩溃,此时Executor也会被Kill掉,那么Executor中的数据就会丢失,此时就必须通过WAL机制让所有的数据通过类似HDFS的方式进行安全性容错处理,从而解决Executor被Kill掉后导致数据丢失的问题。
数据重复读取的场景:
在Receiver收到数据且保存到了HDFS时,如果Receiver崩溃,且此时没有来得及更新ZooKeeper上的offsets,那么Receiver重新启动后就会从管理Kafka的ZooKeeper中再次读取元数据从而导致重复读取元数据;从Spark Streaming来看是成功的,但是Kafka认为是失败的(因为Receiver崩溃时没有及时更新offsets到ZooKeeper中)重新恢复时会重新消费一次,此时会导致数据重新消费的情况。
Spark 1.3之前的版本在这个场景下其实有性能问题:
1. 通过WAL方式保证数据不丢失,但弊端是通过WAL方式会极大的损伤Spark Streaming中的Receiver接收数据的性能(现网生产环境通常会Kafka Direct Api直接处理)。
2. 如果通过Kafka作为数据来源的话,Kafka中有数据,然后Receiver接收数据的时候又会有数据副本,这个时候其实是存储资源的浪费。(重复读取数据解决办法,读取数据时可以将元数据信息放入内存数据库中,再次计算时检查元数据是否被计算过)。
Spark从1.3版本开始,为了避免WAL的性能损失和实现Exactly Once而提供了Kafka Direct Api,把Kafka作为文件存储系统。此时Kafka兼具有流的优势和文件系统的优势,至此,Spark Streaming+Kafka就构建了完美的流处理世界!
数据不需要拷贝副本,不需要WAL性能损耗,不需要Receiver,而直接通过Kafka Direct Api直接消费数据,所有的Executors通过Kafka Api直接消费数据,直接管理offset,所以也不会重复消费数据;事务实现啦!
WAL的弊端:
WAL也不能完全的解决数据丢失的问题,就像Oracle一样,日志文件的写,也是先写到内存中,然后根据一定的触发条件再将数据写到磁盘。如果还没有来的及写WAL日志,此时数据也会有不一致的情况(数据已经接收,但是还没有写到WAL的这部分数据是恢复不出来的。)。
输出不重复
为什么会有这个问题,因为SparkStreaming在计算的时候基于SparkCore,SparkCore天生会在下列场景中导致输出重复:
1.Task、Stage甚至Job重试;
2.慢任务推测;
3.消息偏移量未及时更新;
会导致数据的丢失。
对应的解决方案:
1.一个任务失败就是job 失败,设置spark.task.maxFailures次数为1;不再重试。
2.设置spark.speculation为关闭状态(因为慢任务推测其实非常消耗性能,所以关闭后可以显著的提高Spark Streaming处理性能)
3.Spark streaming on kafka的话,假如job失败后可以设置kafka的auto.offset.reset为largest的方式会自动恢复job的执行。
最后再次强调:
可以通过transform和foreachRDD基于业务逻辑代码进行逻辑控制来实现数据不重复消费和输出不重复!这二个方法类似于spark的后门,可以做任意想象的控制操作!